/* 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 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} }; 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 */ { grub_printf ("Removing the mapping for %s (%02x)", args[0], mapfrom); drivemap_remove (mapfrom); } else /* Map */ { grub_printf ("Mapping %s (%02x) to %02x\n", args[0], mapfrom, (grub_uint8_t)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; #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 ) ) /* 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) return GRUB_ERR_NONE; /* No need to install the int13h handler */ else { grub_uint32_t *ivtslot = (grub_uint32_t*)0x0000004c; /* Save the pointer to the old int13h handler */ grub_drivemap_int13_oldhandler = *ivtslot; grub_printf ("Old int13 handler at %08x\n", grub_drivemap_int13_oldhandler); /* 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; grub_printf ("Top of conventional memory: %u\n", *bpaMemInKb); grub_size_t totalSize = grub_drivemap_int13_size + (entries + 1) * sizeof(int13map_node_t); grub_printf ("Payload is %u bytes long, reserving %u Kb\n", totalSize, (totalSize >> 10) + (totalSize % 1024) == 0 ? 0 : 1); *bpaMemInKb -= (totalSize >> 10) + ((totalSize % 1024) == 0) ? 0 : 1; /* Install the int13h handler (copy to the reserved area) */ grub_uint8_t *handler_base = (grub_uint8_t*)(*bpaMemInKb << 10); grub_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); grub_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; grub_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; grub_printf ("\t#%d: 0x%02x <- 0x%02x (end)\n", i, handler_map[i].disknum, handler_map[i].mapto); /* Install the int13h handler (set the IVT entry for it) */ grub_uint32_t ivtentry = ((grub_uint32_t)handler_base) << 12; /* Segment address */ ivtentry |= (grub_uint16_t) INT13H_OFFSET(grub_drivemap_int13_handler); grub_printf ("New int13 handler IVT pointer: %08x\n", ivtentry); *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"); }