summaryrefslogtreecommitdiffstats
path: root/grub-core/disk/i386/pc/biosdisk.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/disk/i386/pc/biosdisk.c')
-rw-r--r--grub-core/disk/i386/pc/biosdisk.c688
1 files changed, 688 insertions, 0 deletions
diff --git a/grub-core/disk/i386/pc/biosdisk.c b/grub-core/disk/i386/pc/biosdisk.c
new file mode 100644
index 0000000..8ca250c
--- /dev/null
+++ b/grub-core/disk/i386/pc/biosdisk.c
@@ -0,0 +1,688 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/machine/biosdisk.h>
+#include <grub/machine/kernel.h>
+#include <grub/machine/memory.h>
+#include <grub/machine/int.h>
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/err.h>
+#include <grub/term.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static int cd_drive = 0;
+static int grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap);
+
+static int grub_biosdisk_get_num_floppies (void)
+{
+ struct grub_bios_int_registers regs;
+ int drive;
+
+ /* reset the disk system first */
+ regs.eax = 0;
+ regs.edx = 0;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+
+ for (drive = 0; drive < 2; drive++)
+ {
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT | GRUB_CPU_INT_FLAGS_CARRY;
+ regs.edx = drive;
+
+ /* call GET DISK TYPE */
+ regs.eax = 0x1500;
+ grub_bios_interrupt (0x13, &regs);
+ if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
+ break;
+
+ /* check if this drive exists */
+ if (!(regs.eax & 0x300))
+ break;
+ }
+
+ return drive;
+}
+
+/*
+ * Call IBM/MS INT13 Extensions (int 13 %ah=AH) for DRIVE. DAP
+ * is passed for disk address packet. If an error occurs, return
+ * non-zero, otherwise zero.
+ */
+
+static int
+grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap)
+{
+ struct grub_bios_int_registers regs;
+ regs.eax = ah << 8;
+ /* compute the address of disk_address_packet */
+ regs.ds = (((grub_addr_t) dap) & 0xffff0000) >> 4;
+ regs.esi = (((grub_addr_t) dap) & 0xffff);
+ regs.edx = drive;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+ return (regs.eax >> 8) & 0xff;
+}
+
+/*
+ * Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write
+ * NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs,
+ * return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_rw_standard (int ah, int drive, int coff, int hoff,
+ int soff, int nsec, int segment)
+{
+ int ret, i;
+
+ /* Try 3 times. */
+ for (i = 0; i < 3; i++)
+ {
+ struct grub_bios_int_registers regs;
+
+ /* set up CHS information */
+ /* set %ch to low eight bits of cylinder */
+ regs.ecx = (coff << 8) & 0xff00;
+ /* set bits 6-7 of %cl to high two bits of cylinder */
+ regs.ecx |= (coff >> 2) & 0xc0;
+ /* set bits 0-5 of %cl to sector */
+ regs.ecx |= soff & 0x3f;
+
+ /* set %dh to head and %dl to drive */
+ regs.edx = (drive & 0xff) | ((hoff << 8) & 0xff00);
+ /* set %ah to AH */
+ regs.eax = (ah << 8) & 0xff00;
+ /* set %al to NSEC */
+ regs.eax |= nsec & 0xff;
+
+ regs.ebx = 0;
+ regs.es = segment;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+ /* check if successful */
+ if (!(regs.flags & GRUB_CPU_INT_FLAGS_CARRY))
+ return 0;
+
+ /* save return value */
+ ret = regs.eax >> 8;
+
+ /* if fail, reset the disk system */
+ regs.eax = 0;
+ regs.edx = (drive & 0xff);
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+ }
+ return ret;
+}
+
+/*
+ * Check if LBA is supported for DRIVE. If it is supported, then return
+ * the major version of extensions, otherwise zero.
+ */
+static int
+grub_biosdisk_check_int13_extensions (int drive)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.edx = drive & 0xff;
+ regs.eax = 0x4100;
+ regs.ebx = 0x55aa;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
+ return 0;
+
+ if ((regs.ebx & 0xffff) != 0xaa55)
+ return 0;
+
+ /* check if AH=0x42 is supported */
+ if (!(regs.ecx & 1))
+ return 0;
+
+ return (regs.eax >> 8) & 0xff;
+}
+
+/*
+ * Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an
+ * error occurs, then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_diskinfo_standard (int drive,
+ unsigned long *cylinders,
+ unsigned long *heads,
+ unsigned long *sectors)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.eax = 0x0800;
+ regs.edx = drive & 0xff;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ /* Check if unsuccessful. Ignore return value if carry isn't set to
+ workaround some buggy BIOSes. */
+ if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
+ return (regs.eax & 0xff00) >> 8;
+
+ /* bogus BIOSes may not return an error number */
+ /* 0 sectors means no disk */
+ if (!(regs.ecx & 0x3f))
+ /* XXX 0x60 is one of the unused error numbers */
+ return 0x60;
+
+ /* the number of heads is counted from zero */
+ *heads = ((regs.edx >> 8) & 0xff) + 1;
+ *cylinders = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x0300)) + 1;
+ *sectors = regs.ecx & 0x3f;
+ return 0;
+}
+
+static int
+grub_biosdisk_get_diskinfo_real (int drive, void *drp, grub_uint16_t ax)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.eax = ax;
+
+ /* compute the address of drive parameters */
+ regs.esi = ((grub_addr_t) drp) & 0xf;
+ regs.ds = ((grub_addr_t) drp) >> 4;
+ regs.edx = drive & 0xff;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ /* Check if unsuccessful. Ignore return value if carry isn't set to
+ workaround some buggy BIOSes. */
+ if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
+ return (regs.eax & 0xff00) >> 8;
+
+ return 0;
+}
+
+/*
+ * Return the cdrom information of DRIVE in CDRP. If an error occurs,
+ * then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_cdinfo_int13_extensions (int drive, void *cdrp)
+{
+ return grub_biosdisk_get_diskinfo_real (drive, cdrp, 0x4b01);
+}
+
+/*
+ * Return the geometry of DRIVE in a drive parameters, DRP. If an error
+ * occurs, then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_diskinfo_int13_extensions (int drive, void *drp)
+{
+ return grub_biosdisk_get_diskinfo_real (drive, drp, 0x4800);
+}
+
+static int
+grub_biosdisk_get_drive (const char *name)
+{
+ unsigned long drive;
+
+ if (name[0] == 'c' && name[1] == 'd' && name[2] == 0 && cd_drive)
+ return cd_drive;
+
+ if ((name[0] != 'f' && name[0] != 'h') || name[1] != 'd')
+ goto fail;
+
+ drive = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+
+ if (name[0] == 'h')
+ drive += 0x80;
+
+ return (int) drive ;
+
+ fail:
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a biosdisk");
+ return -1;
+}
+
+static int
+grub_biosdisk_call_hook (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ int drive)
+{
+ char name[10];
+
+ if (cd_drive && drive == cd_drive)
+ return hook ("cd", hook_data);
+
+ grub_snprintf (name, sizeof (name),
+ (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80));
+ return hook (name, hook_data);
+}
+
+static int
+grub_biosdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ int num_floppies;
+ int drive;
+
+ /* For hard disks, attempt to read the MBR. */
+ switch (pull)
+ {
+ case GRUB_DISK_PULL_NONE:
+ for (drive = 0x80; drive < 0x90; drive++)
+ {
+ if (grub_biosdisk_rw_standard (0x02, drive, 0, 0, 1, 1,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG) != 0)
+ {
+ grub_dprintf ("disk", "Read error when probing drive 0x%2x\n", drive);
+ break;
+ }
+
+ if (grub_biosdisk_call_hook (hook, hook_data, drive))
+ return 1;
+ }
+ return 0;
+
+ case GRUB_DISK_PULL_REMOVABLE:
+ if (cd_drive)
+ {
+ if (grub_biosdisk_call_hook (hook, hook_data, cd_drive))
+ return 1;
+ }
+
+ /* For floppy disks, we can get the number safely. */
+ num_floppies = grub_biosdisk_get_num_floppies ();
+ for (drive = 0; drive < num_floppies; drive++)
+ if (grub_biosdisk_call_hook (hook, hook_data, drive))
+ return 1;
+ return 0;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+static grub_err_t
+grub_biosdisk_open (const char *name, grub_disk_t disk)
+{
+ grub_uint64_t total_sectors = 0;
+ int drive;
+ struct grub_biosdisk_data *data;
+
+ drive = grub_biosdisk_get_drive (name);
+ if (drive < 0)
+ return grub_errno;
+
+ disk->id = drive;
+
+ data = (struct grub_biosdisk_data *) grub_zalloc (sizeof (*data));
+ if (! data)
+ return grub_errno;
+
+ data->drive = drive;
+
+ if ((cd_drive) && (drive == cd_drive))
+ {
+ data->flags = GRUB_BIOSDISK_FLAG_LBA | GRUB_BIOSDISK_FLAG_CDROM;
+ data->sectors = 8;
+ disk->log_sector_size = 11;
+ /* TODO: get the correct size. */
+ total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ }
+ else
+ {
+ /* HDD */
+ int version;
+
+ disk->log_sector_size = 9;
+
+ version = grub_biosdisk_check_int13_extensions (drive);
+ if (version)
+ {
+ struct grub_biosdisk_drp *drp
+ = (struct grub_biosdisk_drp *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+
+ /* Clear out the DRP. */
+ grub_memset (drp, 0, sizeof (*drp));
+ drp->size = sizeof (*drp);
+ if (! grub_biosdisk_get_diskinfo_int13_extensions (drive, drp))
+ {
+ data->flags = GRUB_BIOSDISK_FLAG_LBA;
+
+ if (drp->total_sectors)
+ total_sectors = drp->total_sectors;
+ else
+ /* Some buggy BIOSes doesn't return the total sectors
+ correctly but returns zero. So if it is zero, compute
+ it by C/H/S returned by the LBA BIOS call. */
+ total_sectors = ((grub_uint64_t) drp->cylinders)
+ * drp->heads * drp->sectors;
+ if (drp->bytes_per_sector
+ && !(drp->bytes_per_sector & (drp->bytes_per_sector - 1))
+ && drp->bytes_per_sector >= 512
+ && drp->bytes_per_sector <= 16384)
+ {
+ for (disk->log_sector_size = 0;
+ (1 << disk->log_sector_size) < drp->bytes_per_sector;
+ disk->log_sector_size++);
+ }
+ }
+ }
+ }
+
+ if (! (data->flags & GRUB_BIOSDISK_FLAG_CDROM))
+ {
+ if (grub_biosdisk_get_diskinfo_standard (drive,
+ &data->cylinders,
+ &data->heads,
+ &data->sectors) != 0)
+ {
+ if (total_sectors && (data->flags & GRUB_BIOSDISK_FLAG_LBA))
+ {
+ data->sectors = 63;
+ data->heads = 255;
+ data->cylinders
+ = grub_divmod64 (total_sectors
+ + data->heads * data->sectors - 1,
+ data->heads * data->sectors, 0);
+ }
+ else
+ {
+ grub_free (data);
+ return grub_error (GRUB_ERR_BAD_DEVICE, "%s cannot get C/H/S values", disk->name);
+ }
+ }
+
+ if (data->sectors == 0)
+ data->sectors = 63;
+ if (data->heads == 0)
+ data->heads = 255;
+
+ if (! total_sectors)
+ total_sectors = ((grub_uint64_t) data->cylinders)
+ * data->heads * data->sectors;
+ }
+
+ disk->total_sectors = total_sectors;
+ /* Limit the max to 0x7f because of Phoenix EDD. */
+ disk->max_agglomerate = 0x7f >> GRUB_DISK_CACHE_BITS;
+ COMPILE_TIME_ASSERT ((0x7f >> GRUB_DISK_CACHE_BITS
+ << (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS))
+ + sizeof (struct grub_biosdisk_dap)
+ < GRUB_MEMORY_MACHINE_SCRATCH_SIZE);
+
+ disk->data = data;
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_biosdisk_close (grub_disk_t disk)
+{
+ grub_free (disk->data);
+}
+
+/* For readability. */
+#define GRUB_BIOSDISK_READ 0
+#define GRUB_BIOSDISK_WRITE 1
+
+#define GRUB_BIOSDISK_CDROM_RETRY_COUNT 3
+
+static grub_err_t
+grub_biosdisk_rw (int cmd, grub_disk_t disk,
+ grub_disk_addr_t sector, grub_size_t size,
+ unsigned segment)
+{
+ struct grub_biosdisk_data *data = disk->data;
+
+ /* VirtualBox fails with sectors above 2T on CDs.
+ Since even BD-ROMS are never that big anyway, return error. */
+ if ((data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ && (sector >> 32))
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_LBA)
+ {
+ struct grub_biosdisk_dap *dap;
+
+ dap = (struct grub_biosdisk_dap *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR
+ + (data->sectors
+ << disk->log_sector_size));
+ dap->length = sizeof (*dap);
+ dap->reserved = 0;
+ dap->blocks = size;
+ dap->buffer = segment << 16; /* The format SEGMENT:ADDRESS. */
+ dap->block = sector;
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ {
+ int i;
+
+ if (cmd)
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("cannot write to CD-ROM"));
+
+ for (i = 0; i < GRUB_BIOSDISK_CDROM_RETRY_COUNT; i++)
+ if (! grub_biosdisk_rw_int13_extensions (0x42, data->drive, dap))
+ break;
+
+ if (i == GRUB_BIOSDISK_CDROM_RETRY_COUNT)
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ }
+ else
+ if (grub_biosdisk_rw_int13_extensions (cmd + 0x42, data->drive, dap))
+ {
+ /* Fall back to the CHS mode. */
+ data->flags &= ~GRUB_BIOSDISK_FLAG_LBA;
+ disk->total_sectors = data->cylinders * data->heads * data->sectors;
+ return grub_biosdisk_rw (cmd, disk, sector, size, segment);
+ }
+ }
+ else
+ {
+ unsigned coff, hoff, soff;
+ unsigned head;
+
+ /* It is impossible to reach over 8064 MiB (a bit less than LBA24) with
+ the traditional CHS access. */
+ if (sector >
+ 1024 /* cylinders */ *
+ 256 /* heads */ *
+ 63 /* spt */)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ soff = ((grub_uint32_t) sector) % data->sectors + 1;
+ head = ((grub_uint32_t) sector) / data->sectors;
+ hoff = head % data->heads;
+ coff = head / data->heads;
+
+ if (coff >= data->cylinders)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ if (grub_biosdisk_rw_standard (cmd + 0x02, data->drive,
+ coff, hoff, soff, size, segment))
+ {
+ switch (cmd)
+ {
+ case GRUB_BIOSDISK_READ:
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ case GRUB_BIOSDISK_WRITE:
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx "
+ "to `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ }
+ }
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+/* Return the number of sectors which can be read safely at a time. */
+static grub_size_t
+get_safe_sectors (grub_disk_t disk, grub_disk_addr_t sector)
+{
+ grub_size_t size;
+ grub_uint64_t offset;
+ struct grub_biosdisk_data *data = disk->data;
+ grub_uint32_t sectors = data->sectors;
+
+ /* OFFSET = SECTOR % SECTORS */
+ grub_divmod64 (sector, sectors, &offset);
+
+ size = sectors - offset;
+
+ return size;
+}
+
+static grub_err_t
+grub_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ while (size)
+ {
+ grub_size_t len;
+
+ len = get_safe_sectors (disk, sector);
+
+ if (len > size)
+ len = size;
+
+ if (grub_biosdisk_rw (GRUB_BIOSDISK_READ, disk, sector, len,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG))
+ return grub_errno;
+
+ grub_memcpy (buf, (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ len << disk->log_sector_size);
+
+ buf += len << disk->log_sector_size;
+ sector += len;
+ size -= len;
+ }
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ struct grub_biosdisk_data *data = disk->data;
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ return grub_error (GRUB_ERR_IO, N_("cannot write to CD-ROM"));
+
+ while (size)
+ {
+ grub_size_t len;
+
+ len = get_safe_sectors (disk, sector);
+ if (len > size)
+ len = size;
+
+ grub_memcpy ((void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, buf,
+ len << disk->log_sector_size);
+
+ if (grub_biosdisk_rw (GRUB_BIOSDISK_WRITE, disk, sector, len,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG))
+ return grub_errno;
+
+ buf += len << disk->log_sector_size;
+ sector += len;
+ size -= len;
+ }
+
+ return grub_errno;
+}
+
+static struct grub_disk_dev grub_biosdisk_dev =
+ {
+ .name = "biosdisk",
+ .id = GRUB_DISK_DEVICE_BIOSDISK_ID,
+ .disk_iterate = grub_biosdisk_iterate,
+ .disk_open = grub_biosdisk_open,
+ .disk_close = grub_biosdisk_close,
+ .disk_read = grub_biosdisk_read,
+ .disk_write = grub_biosdisk_write,
+ .next = 0
+ };
+
+static void
+grub_disk_biosdisk_fini (void)
+{
+ grub_disk_dev_unregister (&grub_biosdisk_dev);
+}
+
+GRUB_MOD_INIT(biosdisk)
+{
+ struct grub_biosdisk_cdrp *cdrp
+ = (struct grub_biosdisk_cdrp *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_uint8_t boot_drive;
+
+ if (grub_disk_firmware_is_tainted)
+ {
+ grub_puts_ (N_("Native disk drivers are in use. "
+ "Refusing to use firmware disk interface."));
+ return;
+ }
+ grub_disk_firmware_fini = grub_disk_biosdisk_fini;
+
+ grub_memset (cdrp, 0, sizeof (*cdrp));
+ cdrp->size = sizeof (*cdrp);
+ cdrp->media_type = 0xFF;
+ boot_drive = (grub_boot_device >> 24);
+ if ((! grub_biosdisk_get_cdinfo_int13_extensions (boot_drive, cdrp))
+ && ((cdrp->media_type & GRUB_BIOSDISK_CDTYPE_MASK)
+ == GRUB_BIOSDISK_CDTYPE_NO_EMUL))
+ cd_drive = cdrp->drive_no;
+ /* Since diskboot.S rejects devices over 0x90 it must be a CD booted with
+ cdboot.S
+ */
+ if (boot_drive >= 0x90)
+ cd_drive = boot_drive;
+
+ grub_disk_dev_register (&grub_biosdisk_dev);
+}
+
+GRUB_MOD_FINI(biosdisk)
+{
+ grub_disk_biosdisk_fini ();
+}