Index: conf/i386-pc.rmk =================================================================== --- conf/i386-pc.rmk (revision 2201) +++ conf/i386-pc.rmk (working copy) @@ -185,8 +185,15 @@ aout.mod bsd.mod pxe.mod pxecmd.mod datetime.mod date.mod \ datehook.mod lsmmap.mod ata_pthru.mod hdparm.mod \ usb.mod uhci.mod ohci.mod usbtest.mod usbms.mod usb_keyboard.mod \ - efiemu.mod mmap.mod acpi.mod + efiemu.mod mmap.mod acpi.mod drivemap.mod +# 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) + # For efiemu.mod. efiemu_mod_SOURCES = efiemu/main.c efiemu/i386/loadcore32.c \ efiemu/i386/loadcore64.c efiemu/i386/pc/cfgtables.c \ Index: commands/i386/pc/drivemap.c =================================================================== --- commands/i386/pc/drivemap.c (revision 0) +++ commands/i386/pc/drivemap.c (revision 0) @@ -0,0 +1,443 @@ +/* drivemap.c - command to manage the BIOS drive mappings. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008, 2009 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 + + +#define MODNAME "drivemap" + +static grub_extcmd_t cmd_reghandle; + +/* Remember to update enum opt_idxs accordingly. */ +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}, + {"swap", 's', 0, "perform both direct and reverse mappings", 0, 0}, + {0, 0, 0, 0, 0, 0} +}; + +/* Remember to update options[] accordingly. */ +enum opt_idxs +{ + OPTIDX_LIST = 0, + OPTIDX_RESET, + OPTIDX_SWAP, +}; + +/* Realmode far ptr (2 * 16b) to the previous INT13h handler. */ +extern grub_uint32_t grub_drivemap_int13_oldhandler; + +/* The type "void" is used for imported assembly labels, takes no storage and + serves just to take the address with &label. Do NOT put void* here. */ +/* Start of the handler bundle. */ +extern const void grub_drivemap_int13_handler_base; +/* Start of the drive mappings area (space reserved at runtime). */ +extern const void grub_drivemap_int13_mapstart; +/* The assembly function to replace the old INT13h handler. It should not be + called because it does not follow any C callspecs and returns with IRET. */ +extern const void grub_drivemap_int13_handler; + +typedef struct drivemap_node +{ + struct drivemap_node *next; + grub_uint8_t newdrive; + grub_uint8_t redirto; +} drivemap_node_t; + +static drivemap_node_t *map_head; +static void *insthandler_hook; +static int handlermem_hnd; +static grub_err_t install_int13_handler (int noret __attribute__ ((unused))); +static grub_err_t uninstall_int13_handler (void); + +/* Puts the specified mapping into the table, replacing an existing mapping + for newdrive or adding a new one if required. */ +static grub_err_t +drivemap_set (grub_uint8_t newdrive, grub_uint8_t redirto) +{ + drivemap_node_t *mapping = 0; + drivemap_node_t *search = map_head; + while (search) + { + if (search->newdrive == newdrive) + { + mapping = search; + break; + } + search = search->next; + } + + /* Check for pre-existing mappings to modify before creating a new one. */ + if (mapping) + mapping->redirto = redirto; + else + { + mapping = grub_malloc (sizeof (drivemap_node_t)); + if (! mapping) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, + "cannot allocate map entry, not enough memory"); + mapping->newdrive = newdrive; + mapping->redirto = redirto; + mapping->next = map_head; + map_head = mapping; + } + return GRUB_ERR_NONE; +} + +/* Removes the mapping for newdrive from the table. If there is no mapping, + then this function behaves like a no-op on the map. */ +static void +drivemap_remove (grub_uint8_t newdrive) +{ + drivemap_node_t *mapping = 0; + drivemap_node_t *search = map_head; + drivemap_node_t *previous = 0; + + while (search) + { + if (search->newdrive == newdrive) + { + mapping = search; + break; + } + previous = search; + search = search->next; + } + + if (mapping) + { + if (previous) + previous->next = mapping->next; + else + map_head = mapping->next; + grub_free (mapping); + } +} + +/* Given a device name, resolves its BIOS disk number and stores it in the + passed location, which should only be trusted if ERR_NONE is returned. */ +static grub_err_t +parse_biosdisk (const char *name, grub_uint8_t * disknum) +{ + grub_disk_t disk; + /* Skip the first ( in (hd0) - disk_open wants just the name. */ + if (*name == '(') + name++; + + disk = grub_disk_open (name); + if (! disk) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown device \"%s\"", name); + const enum grub_disk_dev_id id = disk->dev->id; + /* The following assignment is only sound if the device is indeed a + biosdisk. The caller must check the return value. */ + if (disknum) + *disknum = disk->id; + grub_disk_close (disk); + if (id != GRUB_DISK_DEVICE_BIOSDISK_ID) + return grub_error (GRUB_ERR_BAD_DEVICE, "%s is not a BIOS disk", name); + return GRUB_ERR_NONE; +} + +/* Given a BIOS disk number, returns its GRUB device name if it exists. + If the call succeeds, the resulting device string must be freed. + For nonexisting BIOS disk numbers, this function returns + GRUB_ERR_UNKNOWN_DEVICE. */ +static grub_err_t +revparse_biosdisk (const grub_uint8_t dnum, const char **output) +{ + int found = 0; + auto int find (const char *name); + int find (const char *name) + { + const grub_disk_t disk = grub_disk_open (name); + if (! disk) + return 0; + if (disk->id == dnum && disk->dev->id == GRUB_DISK_DEVICE_BIOSDISK_ID) + { + found = 1; + if (output) + *output = grub_strdup (name); + } + grub_disk_close (disk); + return found; + } + + grub_disk_dev_iterate (find); + if (found) + return GRUB_ERR_NONE; + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "BIOS disk %02x not found", dnum); +} + +/* Given a GRUB-like device name and a convenient location, stores the + related BIOS disk number. Accepts devices like \((f|h)dN\), with + 0 <= N < 128. */ +static grub_err_t +tryparse_diskstring (const char *str, grub_uint8_t * output) +{ + /* Skip opening paren in order to allow both (hd0) and hd0. */ + if (*str == '(') + str++; + if ((str[0] == 'f' || str[0] == 'h') && str[1] == 'd') + { + grub_uint8_t bios_num = (str[0] == 'h') ? 0x80 : 0x00; + unsigned long drivenum = grub_strtoul (str + 2, 0, 0); + if (grub_errno == GRUB_ERR_NONE && drivenum < 128) + { + bios_num |= drivenum; + if (output) + *output = bios_num; + return GRUB_ERR_NONE; + } + } + return grub_error (GRUB_ERR_BAD_ARGUMENT, "device format \"%s\" " + "invalid: must be (f|h)dN, with 0 <= N < 128", str); +} + +static grub_err_t +grub_cmd_drivemap (struct grub_extcmd *cmd, int argc, char **args) +{ + if (cmd->state[OPTIDX_LIST].set || argc == 0) + { + /* Show: list mappings. */ + if (! map_head) + { + grub_printf ("No drives have been remapped"); + return GRUB_ERR_NONE; + } + grub_printf ("Showing only remapped drives.\n"); + grub_printf ("BIOS disk #num ----> GRUB device\n"); + drivemap_node_t *curnode = map_head; + while (curnode) + { + const char *dname = 0; + grub_err_t err = revparse_biosdisk (curnode->redirto, &dname); + if (err != GRUB_ERR_NONE) + return err; + grub_printf ("%cD #%-3u (0x%02x) %s\n", + (curnode->newdrive & 0x80) ? 'H' : 'F', + curnode->newdrive & 0x7F, curnode->newdrive, + dname); + curnode = curnode->next; + grub_free ((char *) dname); + } + return GRUB_ERR_NONE; + } + else if (cmd->state[OPTIDX_RESET].set) + { + /* Reset: just delete all mappings, freeing their memory. */ + drivemap_node_t *curnode = map_head; + drivemap_node_t *prevnode = 0; + while (curnode) + { + prevnode = curnode; + curnode = curnode->next; + grub_free (prevnode); + } + map_head = 0; + return GRUB_ERR_NONE; + } + + /* Neither flag: put mapping. */ + grub_uint8_t mapfrom = 0; + grub_uint8_t mapto = 0xFF; + grub_err_t err; + + if (argc != 2) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "two arguments required"); + + err = parse_biosdisk (args[0], &mapfrom); + if (err != GRUB_ERR_NONE) + return err; + + /* When swapping we require both devices to be BIOS disks, but when + performing direct mappings we only require the 2nd argument to look + like a BIOS disk in order to resolve it into a BIOS disk number. */ + if (cmd->state[OPTIDX_SWAP].set) + err = parse_biosdisk (args[1], &mapto); + else + err = tryparse_diskstring (args[1], &mapto); + if (err != GRUB_ERR_NONE) + return err; + + if (mapto == mapfrom) + { + /* Reset to default. */ + grub_dprintf (MODNAME, "Removing the mapping for %s (%02x)", + args[0], mapfrom); + drivemap_remove (mapfrom); + return GRUB_ERR_NONE; + } + /* Set the mapping for the disk (overwrites any existing mapping). */ + grub_dprintf (MODNAME, "%s %s (%02x) = %s (%02x)\n", + cmd->state[OPTIDX_SWAP].set ? "Swapping" : "Mapping", + args[1], mapto, args[0], mapfrom); + err = drivemap_set (mapto, mapfrom); + /* If -s, perform the reverse mapping too (only if the first was OK). */ + if (cmd->state[OPTIDX_SWAP].set && err == GRUB_ERR_NONE) + err = drivemap_set (mapfrom, mapto); + return err; +} + +typedef struct __attribute__ ((packed)) int13map_node +{ + grub_uint8_t disknum; + grub_uint8_t mapto; +} int13map_node_t; + +#define INT13H_OFFSET(x) ( ((grub_uint8_t*)(x)) - ((grub_uint8_t*)&grub_drivemap_int13_handler_base) ) +#define INT13H_REBASE(x) ( (void*) (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 apart from identity paging, since it accesses + realmode structures by their absolute addresses, like the IVT at 0; + and transforms a pmode pointer into a rmode seg:off far ptr. */ +static grub_err_t +install_int13_handler (int noret __attribute__ ((unused))) +{ + grub_size_t entries = 0; + drivemap_node_t *curentry = map_head; + + /* Count entries to prepare a contiguous map block. */ + while (curentry) + { + entries++; + curentry = curentry->next; + } + if (entries == 0) + { + /* No need to install the int13h handler. */ + grub_dprintf (MODNAME, "No drives marked as remapped, installation " + "of an int13h handler is not required."); + return GRUB_ERR_NONE; + } + else + { + /* Real mode IVT slot (seg:off far pointer) for interrupt 0x13. */ + grub_uint32_t *ivtslot = UINT_TO_PTR (0x0000004c); + /* Size of the full int13 handler "bundle", including code and map. */ + grub_uint64_t total_size; + /* Base address of the space reserved for the handler bundle. */ + grub_uint8_t *handler_base = 0; + /* Address of the map within the deployed bundle. */ + int13map_node_t *handler_map; + /* Real mode IVT entry (seg:off far pointer) for the new handler. */ + grub_uint32_t ivtentry; + + grub_dprintf (MODNAME, "Installing int13h handler...\n"); + + /* Save the pointer to the old handler. */ + grub_drivemap_int13_oldhandler = *ivtslot; + grub_dprintf (MODNAME, "Old int13 handler at %04x:%04x\n", + (grub_drivemap_int13_oldhandler >> 16) & 0x0ffff, + grub_drivemap_int13_oldhandler & 0x0ffff); + + /* Find a rmode-segment-aligned zone in conventional memory big + enough to hold the handler and its data. */ + total_size = INT13H_OFFSET(&grub_drivemap_int13_mapstart) + + (entries + 1) * sizeof (int13map_node_t); + grub_dprintf (MODNAME, "Payload is %llu bytes long\n", total_size); + handler_base = grub_mmap_malign_and_register (16, total_size, + &handlermem_hnd, + GRUB_MACHINE_MEMORY_RESERVED, + GRUB_MMAP_MALLOC_LOW); + if (! handler_base) + return grub_error (GRUB_ERR_OUT_OF_MEMORY, "Could not reserve " + "memory for the int13h handler"); + + /* Copy int13h handler bundle to reserved area. */ + grub_dprintf (MODNAME, "Reserved memory at %p, copying handler...\n", + handler_base); + grub_memcpy (handler_base, &grub_drivemap_int13_handler_base, + INT13H_OFFSET (&grub_drivemap_int13_mapstart)); + + /* Copy the mappings to the reserved area. */ + curentry = map_head; + grub_size_t i; + handler_map = INT13H_TONEWADDR (&grub_drivemap_int13_mapstart); + grub_dprintf (MODNAME, "Target map at %p, copying mappings...\n", + handler_map); + for (i = 0; i < entries; ++i, curentry = curentry->next) + { + handler_map[i].disknum = curentry->newdrive; + handler_map[i].mapto = curentry->redirto; + grub_dprintf (MODNAME, "\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; + grub_dprintf (MODNAME, "\t#%d: 0x00 <- 0x00 (end)\n", i); + + /* Install our function as the int13h handler in the IVT. */ + ivtentry = ((grub_uint32_t) handler_base) << 12; /* Segment address. */ + ivtentry |= + (grub_uint16_t) INT13H_OFFSET (&grub_drivemap_int13_handler); + grub_dprintf (MODNAME, "New int13 handler IVT pointer: %04x:%04x\n", + (ivtentry >> 16) & 0x0ffff, ivtentry & 0x0ffff); + *ivtslot = ivtentry; + + return GRUB_ERR_NONE; + } +} + +static grub_err_t +uninstall_int13_handler (void) +{ + grub_uint32_t *ivtslot = UINT_TO_PTR (0x0000004c); + + if (grub_drivemap_int13_oldhandler != 0) + return GRUB_ERR_NONE; + + *ivtslot = grub_drivemap_int13_oldhandler; + grub_mmap_free_and_unregister (handlermem_hnd); + grub_drivemap_int13_oldhandler = 0; + + return GRUB_ERR_NONE; +} + +GRUB_MOD_INIT (drivemap) +{ + cmd_reghandle = grub_register_extcmd (MODNAME, grub_cmd_drivemap, + GRUB_COMMAND_FLAG_BOTH, + MODNAME + " -l | -r | [-s] grubdev biosdisk", + "Manage the BIOS drive mappings", + options); + insthandler_hook = + grub_loader_register_preboot_hook (&install_int13_handler, + &uninstall_int13_handler, + GRUB_LOADER_PREBOOT_HOOK_PRIO_NORMAL); +} + +GRUB_MOD_FINI (drivemap) +{ + grub_loader_unregister_preboot_hook (insthandler_hook); + insthandler_hook = 0; + grub_unregister_extcmd (cmd_reghandle); +} Index: commands/i386/pc/drivemap_int13h.S =================================================================== --- commands/i386/pc/drivemap_int13h.S (revision 0) +++ commands/i386/pc/drivemap_int13h.S (revision 0) @@ -0,0 +1,70 @@ +/* drivemap_int13h.S - interrupt handler for the BIOS drive remapper */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008, 2009 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 + +#define INT13H_OFFSET(x) ((x) - grub_drivemap_int13_handler_base) + +.code16 + +/* Copy starts here. When deployed, this label must be segment-aligned. */ +VARIABLE(grub_drivemap_int13_handler_base) + +/* Far pointer to the old handler. Stored as a CS:IP in the style of real-mode + IVT entries (thus PI:SC in mem). */ +VARIABLE(grub_drivemap_int13_oldhandler) + .word 0x0, 0x0 + +/* Actual int13 handler. We use relative addressing with CS in order to be as + unintrusive as possible with registers and the stack. */ +FUNCTION(grub_drivemap_int13_handler) + /* Map the drive number (always in DL). */ + push %ax + push %bx + push %si + movw $INT13H_OFFSET(grub_drivemap_int13_mapstart), %bx + xorw %si, %si + +more_remaining: + movw %cs:(%bx,%si), %ax + cmpb %ah, %al + jz not_found /* DRV=DST => map end - drive not remapped, keep DL. */ + cmpb %dl, %al + jz found /* Found - drive remapped, modify DL. */ + addw $2, %si + jmp more_remaining /* Not found, but more remaining, loop. */ + +found: + movb %ah, %dl + +not_found: + pop %si + pop %bx + pop %ax + + /* Upon arrival to this point the stack must be exactly like at entry. + This long jump will transfer the caller's stack to the old INT13 + handler, thus making it return directly to the original caller. */ + ljmp *%cs:INT13H_OFFSET(grub_drivemap_int13_oldhandler) + + +/* 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)