/* drivemap.c - command to manage the BIOS drive mappings. */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2008, 2009 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* Remember to update enum opt_idxs accordingly. */ static const struct VasEBoot_arg_option options[] = { /* TRANSLATORS: In this file "mapping" refers to a change VAS_EBOOT makes so if your language doesn't have an equivalent of "mapping" you can use the word like "rerouting". */ {"list", 'l', 0, N_("Show the current mappings."), 0, 0}, {"reset", 'r', 0, N_("Reset all mappings to the default values."), 0, 0}, {"swap", 's', 0, N_("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 VasEBoot_uint32_t VasEBoot_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 VasEBoot_drivemap_handler; /* Start of the drive mappings area (space reserved at runtime). */ extern const void VasEBoot_drivemap_mapstart; typedef struct drivemap_node { struct drivemap_node *next; VasEBoot_uint8_t newdrive; VasEBoot_uint8_t redirto; } drivemap_node_t; typedef struct VAS_EBOOT_PACKED int13map_node { VasEBoot_uint8_t disknum; VasEBoot_uint8_t mapto; } int13map_node_t; #define INT13H_OFFSET(x) \ (((VasEBoot_uint8_t *)(x)) - ((VasEBoot_uint8_t *)&VasEBoot_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 VasEBoot_err_t drivemap_set (VasEBoot_uint8_t newdrive, VasEBoot_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 = VasEBoot_malloc (sizeof (drivemap_node_t)); if (! mapping) return VasEBoot_errno; mapping->newdrive = newdrive; mapping->redirto = redirto; mapping->next = map_head; map_head = mapping; } return VAS_EBOOT_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 (VasEBoot_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; VasEBoot_free (mapping); } } /* Given a VAS_EBOOT-like device name and a convenient location, stores the related BIOS disk number. Accepts devices like \((f|h)dN\), with 0 <= N < 128. */ static VasEBoot_err_t tryparse_diskstring (const char *str, VasEBoot_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') { VasEBoot_uint8_t bios_num = (str[0] == 'h') ? 0x80 : 0x00; unsigned long drivenum = VasEBoot_strtoul (str + 2, 0, 0); if (VasEBoot_errno == VAS_EBOOT_ERR_NONE && drivenum < 128) { bios_num |= drivenum; if (output) *output = bios_num; return VAS_EBOOT_ERR_NONE; } } return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, "device format \"%s\" " "invalid: must be (f|h)dN, with 0 <= N < 128", str); } static VasEBoot_err_t list_mappings (void) { /* Show: list mappings. */ if (! map_head) { VasEBoot_puts_ (N_("No drives have been remapped")); return VAS_EBOOT_ERR_NONE; } /* TRANSLATORS: This is the header of mapping list. On the left is how OS will see the disks and on the right current VAS_EBOOT vision. */ VasEBoot_puts_ (N_("OS disk #num ------> VAS_EBOOT/BIOS device")); drivemap_node_t *curnode = map_head; while (curnode) { VasEBoot_printf ("%cD #%-3u (0x%02x) %cd%d\n", (curnode->newdrive & 0x80) ? 'H' : 'F', curnode->newdrive & 0x7F, curnode->newdrive, (curnode->redirto & 0x80) ? 'h' : 'f', curnode->redirto & 0x7F ); curnode = curnode->next; } return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t VasEBoot_cmd_drivemap (struct VasEBoot_extcmd_context *ctxt, int argc, char **args) { if (ctxt->state[OPTIDX_LIST].set) { return list_mappings (); } else if (ctxt->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; VasEBoot_free (prevnode); } map_head = 0; return VAS_EBOOT_ERR_NONE; } else if (!ctxt->state[OPTIDX_SWAP].set && argc == 0) { /* No arguments */ return list_mappings (); } /* Neither flag: put mapping. */ VasEBoot_uint8_t mapfrom = 0; VasEBoot_uint8_t mapto = 0xFF; VasEBoot_err_t err; if (argc != 2) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("two arguments expected")); err = tryparse_diskstring (args[0], &mapfrom); if (err != VAS_EBOOT_ERR_NONE) return err; err = tryparse_diskstring (args[1], &mapto); if (err != VAS_EBOOT_ERR_NONE) return err; if (mapto == mapfrom) { /* Reset to default. */ VasEBoot_dprintf ("drivemap", "Removing mapping for %s (%02x)\n", args[0], mapfrom); drivemap_remove (mapfrom); return VAS_EBOOT_ERR_NONE; } /* Set the mapping for the disk (overwrites any existing mapping). */ VasEBoot_dprintf ("drivemap", "%s %s (%02x) = %s (%02x)\n", ctxt->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 (ctxt->state[OPTIDX_SWAP].set && err == VAS_EBOOT_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 VAS_EBOOT 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 VasEBoot_err_t install_int13_handler (int noret __attribute__ ((unused))) { /* Size of the full int13 handler "bundle", including code and map. */ VasEBoot_uint32_t total_size; /* Base address of the space reserved for the handler bundle. */ VasEBoot_uint8_t *handler_base = 0; /* Address of the map within the deployed bundle. */ int13map_node_t *handler_map; /* Real mode IVT slot (seg:off far pointer) for interrupt 0x13. */ VasEBoot_uint32_t *int13slot = (VasEBoot_uint32_t *) VasEBoot_absolute_pointer (4 * 0x13); 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. */ VasEBoot_dprintf ("drivemap", "No drives marked as remapped, not installing " "our int13h handler.\n"); return VAS_EBOOT_ERR_NONE; } VasEBoot_dprintf ("drivemap", "Installing our int13h handler\n"); /* Save the pointer to the old handler. */ VasEBoot_drivemap_oldhandler = *int13slot; VasEBoot_dprintf ("drivemap", "Original int13 handler: %04x:%04x\n", (VasEBoot_drivemap_oldhandler >> 16) & 0x0ffff, VasEBoot_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 (&VasEBoot_drivemap_mapstart) + (entries + 1) * sizeof (int13map_node_t); VasEBoot_dprintf ("drivemap", "Payload is %u bytes long\n", total_size); handler_base = VasEBoot_mmap_malign_and_register (16, ALIGN_UP (total_size, 16), &drivemap_mmap, VAS_EBOOT_MEMORY_RESERVED, VAS_EBOOT_MMAP_MALLOC_LOW); if (! handler_base) return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_MEMORY, "couldn't reserve " "memory for the int13h handler"); /* Copy int13h handler bundle to reserved area. */ VasEBoot_dprintf ("drivemap", "Reserved memory at %p, copying handler\n", handler_base); VasEBoot_memcpy (handler_base, &VasEBoot_drivemap_handler, INT13H_OFFSET (&VasEBoot_drivemap_mapstart)); /* Copy the mappings to the reserved area. */ curentry = map_head; handler_map = (int13map_node_t *) (handler_base + INT13H_OFFSET (&VasEBoot_drivemap_mapstart)); VasEBoot_dprintf ("drivemap", "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; VasEBoot_dprintf ("drivemap", "\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; VasEBoot_dprintf ("drivemap", "\t#%d: 0x00 <- 0x00 (end)\n", i); /* Install our function as the int13h handler in the IVT. */ *int13slot = ((VasEBoot_uint32_t) handler_base) << 12; /* Segment address. */ VasEBoot_dprintf ("drivemap", "New int13 handler: %04x:%04x\n", (*int13slot >> 16) & 0x0ffff, *int13slot & 0x0ffff); return VAS_EBOOT_ERR_NONE; } static VasEBoot_err_t uninstall_int13_handler (void) { /* Real mode IVT slot (seg:off far pointer) for interrupt 0x13. */ VasEBoot_uint32_t *int13slot = (VasEBoot_uint32_t *) VasEBoot_absolute_pointer (4 * 0x13); if (! VasEBoot_drivemap_oldhandler) return VAS_EBOOT_ERR_NONE; *int13slot = VasEBoot_drivemap_oldhandler; VasEBoot_mmap_free_and_unregister (drivemap_mmap); VasEBoot_drivemap_oldhandler = 0; VasEBoot_dprintf ("drivemap", "Restored int13 handler: %04x:%04x\n", (*int13slot >> 16) & 0x0ffff, *int13slot & 0x0ffff); return VAS_EBOOT_ERR_NONE; } static int VasEBoot_get_root_biosnumber_drivemap (void) { const char *biosnum; int ret = -1; VasEBoot_device_t dev; biosnum = VasEBoot_env_get ("biosnum"); if (biosnum) return VasEBoot_strtoul (biosnum, 0, 0); dev = VasEBoot_device_open (0); if (dev && dev->disk && dev->disk->dev && dev->disk->dev->id == VAS_EBOOT_DISK_DEVICE_BIOSDISK_ID) { drivemap_node_t *curnode = map_head; ret = (int) dev->disk->id; while (curnode) { if (curnode->redirto == ret) { ret = curnode->newdrive; break; } curnode = curnode->next; } } if (dev) VasEBoot_device_close (dev); return ret; } static VasEBoot_extcmd_t cmd; static int (*VasEBoot_get_root_biosnumber_saved) (void); VAS_EBOOT_MOD_INIT (drivemap) { VasEBoot_get_root_biosnumber_saved = VasEBoot_get_root_biosnumber; VasEBoot_get_root_biosnumber = VasEBoot_get_root_biosnumber_drivemap; cmd = VasEBoot_register_extcmd ("drivemap", VasEBoot_cmd_drivemap, 0, N_("-l | -r | [-s] VasEBootdev osdisk."), N_("Manage the BIOS drive mappings."), options); drivemap_hook = VasEBoot_loader_register_preboot_hook (&install_int13_handler, &uninstall_int13_handler, VAS_EBOOT_LOADER_PREBOOT_HOOK_PRIO_NORMAL); } VAS_EBOOT_MOD_FINI (drivemap) { VasEBoot_get_root_biosnumber = VasEBoot_get_root_biosnumber_saved; VasEBoot_loader_unregister_preboot_hook (drivemap_hook); drivemap_hook = 0; VasEBoot_unregister_extcmd (cmd); }