From 4246b8a9e8cea3b467af836828e1d06e78cf5b94 Mon Sep 17 00:00:00 2001 From: proski Date: Thu, 14 May 2009 01:23:49 +0000 Subject: [PATCH] =?UTF-8?q?2009-05-13=20=20Javier=20Mart=C3=ADn=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commands/i386/pc/drivemap.c: New file - implement drivemap command. * commands/i386/pc/drivemap_int13h.S: New file - int13 handler. * conf/i386-pc.rmk: Add drivemap.c and drivemap_int13h.S. --- ChangeLog | 7 + commands/i386/pc/drivemap.c | 445 +++++++++++++++++++++++++++++ commands/i386/pc/drivemap_int13h.S | 66 +++++ conf/i386-pc.rmk | 9 +- 4 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 commands/i386/pc/drivemap.c create mode 100644 commands/i386/pc/drivemap_int13h.S diff --git a/ChangeLog b/ChangeLog index 919ec4ba0..5b90c2502 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2009-05-13 Javier Martín + + * commands/i386/pc/drivemap.c: New file - implement drivemap + command. + * commands/i386/pc/drivemap_int13h.S: New file - int13 handler. + * conf/i386-pc.rmk: Add drivemap.c and drivemap_int13h.S. + 2009-05-13 Pavel Roskin * util/i386/pc/grub-setup.c (setup): Remove unused variable diff --git a/commands/i386/pc/drivemap.c b/commands/i386/pc/drivemap.c new file mode 100644 index 000000000..f9710b8d9 --- /dev/null +++ b/commands/i386/pc/drivemap.c @@ -0,0 +1,445 @@ +/* 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 + + +#define MODNAME "drivemap" + +/* Real mode IVT slot (seg:off far pointer) for interrupt 0x13. */ +static grub_uint32_t *const int13slot = UINT_TO_PTR (4 * 0x13); + +/* 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_oldhandler; + +/* The type "void" is used for imported assembly labels, takes no storage and + serves just to take the address with &label. */ + +/* The assembly function to replace the old INT13h handler. It does not follow + any C callspecs and returns with IRET. */ +extern const void grub_drivemap_handler; + +/* Start of the drive mappings area (space reserved at runtime). */ +extern const void grub_drivemap_mapstart; + +typedef struct drivemap_node +{ + struct drivemap_node *next; + grub_uint8_t newdrive; + grub_uint8_t redirto; +} drivemap_node_t; + +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_handler)) + +static drivemap_node_t *map_head; +static void *drivemap_hook; +static int drivemap_mmap; + +/* 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 +list_mappings (void) +{ + /* Show: list mappings. */ + if (! map_head) + { + grub_printf ("No drives have been remapped\n"); + return GRUB_ERR_NONE; + } + + grub_printf ("OS disk #num ------> GRUB/BIOS 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; +} + +static grub_err_t +grub_cmd_drivemap (struct grub_extcmd *cmd, int argc, char **args) +{ + if (cmd->state[OPTIDX_LIST].set) + { + return list_mappings (); + } + 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; + } + else if (!cmd->state[OPTIDX_SWAP].set && argc == 0) + { + /* No arguments */ + return list_mappings (); + } + + /* 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 mapping for %s (%02x)\n", + 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; +} + +/* 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))) +{ + /* Size of the full int13 handler "bundle", including code and map. */ + grub_uint32_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; + + int i; + int 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, not installing " + "our int13h handler.\n"); + return GRUB_ERR_NONE; + } + + grub_dprintf (MODNAME, "Installing our int13h handler\n"); + + /* Save the pointer to the old handler. */ + grub_drivemap_oldhandler = *int13slot; + grub_dprintf (MODNAME, "Original int13 handler: %04x:%04x\n", + (grub_drivemap_oldhandler >> 16) & 0x0ffff, + grub_drivemap_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_mapstart) + + (entries + 1) * sizeof (int13map_node_t); + grub_dprintf (MODNAME, "Payload is %u bytes long\n", total_size); + handler_base = grub_mmap_malign_and_register (16, total_size, + &drivemap_mmap, + 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_handler, + INT13H_OFFSET (&grub_drivemap_mapstart)); + + /* Copy the mappings to the reserved area. */ + curentry = map_head; + handler_map = (int13map_node_t *) (handler_base + + INT13H_OFFSET (&grub_drivemap_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. */ + *int13slot = ((grub_uint32_t) handler_base) << 12; /* Segment address. */ + grub_dprintf (MODNAME, "New int13 handler: %04x:%04x\n", + (*int13slot >> 16) & 0x0ffff, *int13slot & 0x0ffff); + + return GRUB_ERR_NONE; +} + +static grub_err_t +uninstall_int13_handler (void) +{ + if (! grub_drivemap_oldhandler) + return GRUB_ERR_NONE; + + *int13slot = grub_drivemap_oldhandler; + grub_mmap_free_and_unregister (drivemap_mmap); + grub_drivemap_oldhandler = 0; + grub_dprintf (MODNAME, "Restored int13 handler: %04x:%04x\n", + (*int13slot >> 16) & 0x0ffff, *int13slot & 0x0ffff); + + return GRUB_ERR_NONE; +} + +static grub_extcmd_t cmd; + +GRUB_MOD_INIT (drivemap) +{ + cmd = grub_register_extcmd (MODNAME, grub_cmd_drivemap, + GRUB_COMMAND_FLAG_BOTH, + MODNAME + " -l | -r | [-s] grubdev osdisk", + "Manage the BIOS drive mappings", + options); + drivemap_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 (drivemap_hook); + drivemap_hook = 0; + grub_unregister_extcmd (cmd); +} diff --git a/commands/i386/pc/drivemap_int13h.S b/commands/i386/pc/drivemap_int13h.S new file mode 100644 index 000000000..7db3a6793 --- /dev/null +++ b/commands/i386/pc/drivemap_int13h.S @@ -0,0 +1,66 @@ +/* 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_handler) + +.code16 + +/* Copy starts here. When deployed, this code must be segment-aligned. */ + +/* The replacement int13 handler. The code must be position independent. + Preserve all registers. */ +FUNCTION(grub_drivemap_handler) + /* Map the drive number (always in DL). */ + push %ax + push %bx + movw $INT13H_OFFSET(grub_drivemap_mapstart), %bx + +more_remaining: + movw %cs:(%bx), %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. */ + inc %bx + inc %bx + jmp more_remaining /* Not found, but more remaining, loop. */ + +found: + movb %ah, %dl + +not_found: + 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. */ + .byte 0xea + +/* 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_oldhandler) + .word 0x0, 0x0 + +/* 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_mapstart) diff --git a/conf/i386-pc.rmk b/conf/i386-pc.rmk index 222b66228..d51fa9b02 100644 --- a/conf/i386-pc.rmk +++ b/conf/i386-pc.rmk @@ -185,7 +185,14 @@ pkglib_MODULES = biosdisk.mod chain.mod \ 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 \