aboutsummaryrefslogtreecommitdiffstats
path: root/grub-core/disk
diff options
context:
space:
mode:
authorJames <james.mckenzie@citrix.com>2012-11-16 10:41:01 +0000
committerJames <james.mckenzie@citrix.com>2012-11-16 10:41:01 +0000
commit041d1ea37802bf7178a31a53f96c26efa6b8fb7b (patch)
treec193e84ad1237f25a79d0f6a267722e44c73f56a /grub-core/disk
downloadgrub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.gz
grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.tar.bz2
grub-1.99-041d1ea37802bf7178a31a53f96c26efa6b8fb7b.zip
fish
Diffstat (limited to 'grub-core/disk')
-rw-r--r--grub-core/disk/ata.c933
-rw-r--r--grub-core/disk/ata_pthru.c108
-rw-r--r--grub-core/disk/dmraid_nvidia.c171
-rw-r--r--grub-core/disk/efi/efidisk.c850
-rw-r--r--grub-core/disk/host.c95
-rw-r--r--grub-core/disk/i386/pc/biosdisk.c641
-rw-r--r--grub-core/disk/ieee1275/nand.c216
-rw-r--r--grub-core/disk/ieee1275/ofdisk.c373
-rw-r--r--grub-core/disk/loopback.c232
-rw-r--r--grub-core/disk/lvm.c896
-rw-r--r--grub-core/disk/mdraid1x_linux.c247
-rw-r--r--grub-core/disk/mdraid_linux.c248
-rw-r--r--grub-core/disk/memdisk.c117
-rw-r--r--grub-core/disk/raid.c812
-rw-r--r--grub-core/disk/raid5_recover.c76
-rw-r--r--grub-core/disk/raid6_recover.c230
-rw-r--r--grub-core/disk/scsi.c606
-rw-r--r--grub-core/disk/usbms.c441
18 files changed, 7292 insertions, 0 deletions
diff --git a/grub-core/disk/ata.c b/grub-core/disk/ata.c
new file mode 100644
index 0000000..391ccb9
--- /dev/null
+++ b/grub-core/disk/ata.c
@@ -0,0 +1,933 @@
+/* ata.c - ATA disk access. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2007, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/ata.h>
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/pci.h>
+#include <grub/scsi.h>
+#include <grub/cs5536.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* At the moment, only two IDE ports are supported. */
+static const grub_port_t grub_ata_ioaddress[] = { GRUB_ATA_CH0_PORT1,
+ GRUB_ATA_CH1_PORT1 };
+static const grub_port_t grub_ata_ioaddress2[] = { GRUB_ATA_CH0_PORT2,
+ GRUB_ATA_CH1_PORT2 };
+
+static struct grub_ata_device *grub_ata_devices;
+
+/* Wait for !BSY. */
+grub_err_t
+grub_ata_wait_not_busy (struct grub_ata_device *dev, int milliseconds)
+{
+ /* ATA requires 400ns (after a write to CMD register) or
+ 1 PIO cycle (after a DRQ block transfer) before
+ first check of BSY. */
+ grub_millisleep (1);
+
+ int i = 1;
+ grub_uint8_t sts;
+ while ((sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS))
+ & GRUB_ATA_STATUS_BUSY)
+ {
+ if (i >= milliseconds)
+ {
+ grub_dprintf ("ata", "timeout: %dms, status=0x%x\n",
+ milliseconds, sts);
+ return grub_error (GRUB_ERR_TIMEOUT, "ATA timeout");
+ }
+
+ grub_millisleep (1);
+ i++;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static inline void
+grub_ata_wait (void)
+{
+ grub_millisleep (50);
+}
+
+/* Wait for !BSY, DRQ. */
+grub_err_t
+grub_ata_wait_drq (struct grub_ata_device *dev, int rw,
+ int milliseconds)
+{
+ if (grub_ata_wait_not_busy (dev, milliseconds))
+ return grub_errno;
+
+ /* !DRQ implies error condition. */
+ grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
+ if ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ != GRUB_ATA_STATUS_DRQ)
+ {
+ grub_dprintf ("ata", "ata error: status=0x%x, error=0x%x\n",
+ sts, grub_ata_regget (dev, GRUB_ATA_REG_ERROR));
+ if (! rw)
+ return grub_error (GRUB_ERR_READ_ERROR, "ATA read error");
+ else
+ return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+/* Byteorder has to be changed before strings can be read. */
+static void
+grub_ata_strncpy (char *dst, char *src, grub_size_t len)
+{
+ grub_uint16_t *src16 = (grub_uint16_t *) src;
+ grub_uint16_t *dst16 = (grub_uint16_t *) dst;
+ unsigned int i;
+
+ for (i = 0; i < len / 2; i++)
+ *(dst16++) = grub_be_to_cpu16 (*(src16++));
+ dst[len] = '\0';
+}
+
+void
+grub_ata_pio_read (struct grub_ata_device *dev, char *buf, grub_size_t size)
+{
+ grub_uint16_t *buf16 = (grub_uint16_t *) buf;
+ unsigned int i;
+
+ /* Read in the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ buf16[i] = grub_le_to_cpu16 (grub_inw(dev->ioaddress + GRUB_ATA_REG_DATA));
+}
+
+static void
+grub_ata_pio_write (struct grub_ata_device *dev, char *buf, grub_size_t size)
+{
+ grub_uint16_t *buf16 = (grub_uint16_t *) buf;
+ unsigned int i;
+
+ /* Write the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ grub_outw(grub_cpu_to_le16 (buf16[i]), dev->ioaddress + GRUB_ATA_REG_DATA);
+}
+
+static void
+grub_ata_dumpinfo (struct grub_ata_device *dev, char *info)
+{
+ char text[41];
+
+ /* The device information was read, dump it for debugging. */
+ grub_ata_strncpy (text, info + 20, 20);
+ grub_dprintf ("ata", "Serial: %s\n", text);
+ grub_ata_strncpy (text, info + 46, 8);
+ grub_dprintf ("ata", "Firmware: %s\n", text);
+ grub_ata_strncpy (text, info + 54, 40);
+ grub_dprintf ("ata", "Model: %s\n", text);
+
+ if (! dev->atapi)
+ {
+ grub_dprintf ("ata", "Addressing: %d\n", dev->addr);
+ grub_dprintf ("ata", "Sectors: %lld\n", (unsigned long long) dev->size);
+ }
+}
+
+static grub_err_t
+grub_atapi_identify (struct grub_ata_device *dev)
+{
+ char *info;
+
+ info = grub_malloc (GRUB_DISK_SECTOR_SIZE);
+ if (! info)
+ return grub_errno;
+
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4);
+ grub_ata_wait ();
+ if ((grub_ata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
+ && grub_ata_wait_not_busy (dev, dev->present ? GRUB_ATA_TOUT_DEV_INIT
+ : GRUB_ATA_TOUT_STD))
+ {
+ grub_free (info);
+ dev->present = 0;
+ return grub_errno;
+ }
+
+ grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_IDENTIFY_PACKET_DEVICE);
+ grub_ata_wait ();
+
+ if (grub_ata_wait_drq (dev, 0, dev->present ? GRUB_ATA_TOUT_DEV_INIT
+ : GRUB_ATA_TOUT_STD))
+ {
+ grub_free (info);
+ dev->present = 0;
+ return grub_errno;
+ }
+ grub_ata_pio_read (dev, info, GRUB_DISK_SECTOR_SIZE);
+
+ dev->atapi = 1;
+
+ grub_ata_dumpinfo (dev, info);
+
+ grub_free (info);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_atapi_wait_drq (struct grub_ata_device *dev,
+ grub_uint8_t ireason,
+ int milliseconds)
+{
+ /* Wait for !BSY, DRQ, ireason */
+ if (grub_ata_wait_not_busy (dev, milliseconds))
+ return grub_errno;
+
+ grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
+ grub_uint8_t irs = grub_ata_regget (dev, GRUB_ATAPI_REG_IREASON);
+
+ /* OK if DRQ is asserted and interrupt reason is as expected. */
+ if ((sts & GRUB_ATA_STATUS_DRQ)
+ && (irs & GRUB_ATAPI_IREASON_MASK) == ireason)
+ return GRUB_ERR_NONE;
+
+ /* !DRQ implies error condition. */
+ grub_dprintf ("ata", "atapi error: status=0x%x, ireason=0x%x, error=0x%x\n",
+ sts, irs, grub_ata_regget (dev, GRUB_ATA_REG_ERROR));
+
+ if (! (sts & GRUB_ATA_STATUS_DRQ)
+ && (irs & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_ERROR)
+ {
+ if (ireason == GRUB_ATAPI_IREASON_CMD_OUT)
+ return grub_error (GRUB_ERR_READ_ERROR, "ATA PACKET command error");
+ else
+ return grub_error (GRUB_ERR_READ_ERROR, "ATAPI read error");
+ }
+
+ return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
+}
+
+static grub_err_t
+grub_atapi_packet (struct grub_ata_device *dev, char *packet,
+ grub_size_t size)
+{
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
+ if (grub_ata_check_ready (dev))
+ return grub_errno;
+
+ /* Send ATA PACKET command. */
+ grub_ata_regset (dev, GRUB_ATA_REG_FEATURES, 0);
+ grub_ata_regset (dev, GRUB_ATAPI_REG_IREASON, 0);
+ grub_ata_regset (dev, GRUB_ATAPI_REG_CNTHIGH, size >> 8);
+ grub_ata_regset (dev, GRUB_ATAPI_REG_CNTLOW, size & 0xFF);
+
+ grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_PACKET);
+
+ /* Wait for !BSY, DRQ, !I/O, C/D. */
+ if (grub_atapi_wait_drq (dev, GRUB_ATAPI_IREASON_CMD_OUT, GRUB_ATA_TOUT_STD))
+ return grub_errno;
+
+ /* Write the packet. */
+ grub_ata_pio_write (dev, packet, 12);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ata_identify (struct grub_ata_device *dev)
+{
+ char *info;
+ grub_uint16_t *info16;
+
+ info = grub_malloc (GRUB_DISK_SECTOR_SIZE);
+ if (! info)
+ return grub_errno;
+
+ info16 = (grub_uint16_t *) info;
+
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4);
+ grub_ata_wait ();
+ if ((grub_ata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
+ && grub_ata_wait_not_busy (dev, dev->present ? GRUB_ATA_TOUT_DEV_INIT
+ : GRUB_ATA_TOUT_STD))
+ {
+ dev->present = 0;
+ grub_free (info);
+ return grub_errno;
+ }
+
+ grub_ata_regset (dev, GRUB_ATA_REG_CMD, GRUB_ATA_CMD_IDENTIFY_DEVICE);
+ grub_ata_wait ();
+
+ if (grub_ata_wait_drq (dev, 0, dev->present ? GRUB_ATA_TOUT_DEV_INIT
+ : GRUB_ATA_TOUT_STD))
+ {
+ grub_free (info);
+ grub_errno = GRUB_ERR_NONE;
+ grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
+
+ if ((sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ
+ | GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_ERR
+ && (grub_ata_regget (dev, GRUB_ATA_REG_ERROR) & 0x04 /* ABRT */))
+ /* Device without ATA IDENTIFY, try ATAPI. */
+ return grub_atapi_identify (dev);
+
+ else if (sts == 0x00)
+ {
+ dev->present = 0;
+ /* No device, return error but don't print message. */
+ return GRUB_ERR_UNKNOWN_DEVICE;
+ }
+ else
+ {
+ dev->present = 0;
+ /* Other Error. */
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "device cannot be identified");
+ }
+ }
+
+ grub_ata_pio_read (dev, info, GRUB_DISK_SECTOR_SIZE);
+
+ /* Re-check status to avoid bogus identify data due to stuck DRQ. */
+ grub_uint8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
+ if (sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ {
+ grub_dprintf ("ata", "bad status=0x%x\n", sts);
+ grub_free (info);
+ /* No device, return error but don't print message. */
+ grub_errno = GRUB_ERR_NONE;
+ return GRUB_ERR_UNKNOWN_DEVICE;
+ }
+
+ /* Now it is certain that this is not an ATAPI device. */
+ dev->atapi = 0;
+
+ /* CHS is always supported. */
+ dev->addr = GRUB_ATA_CHS;
+
+ /* Check if LBA is supported. */
+ if (info16[49] & (1 << 9))
+ {
+ /* Check if LBA48 is supported. */
+ if (info16[83] & (1 << 10))
+ dev->addr = GRUB_ATA_LBA48;
+ else
+ dev->addr = GRUB_ATA_LBA;
+ }
+
+ /* Determine the amount of sectors. */
+ if (dev->addr != GRUB_ATA_LBA48)
+ dev->size = grub_le_to_cpu32(*((grub_uint32_t *) &info16[60]));
+ else
+ dev->size = grub_le_to_cpu64(*((grub_uint64_t *) &info16[100]));
+
+ /* Read CHS information. */
+ dev->cylinders = info16[1];
+ dev->heads = info16[3];
+ dev->sectors_per_track = info16[6];
+
+ grub_ata_dumpinfo (dev, info);
+
+ grub_free(info);
+
+ return 0;
+}
+
+static grub_err_t
+check_device (struct grub_ata_device *dev)
+{
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
+ grub_ata_wait ();
+
+ /* Try to detect if the port is in use by writing to it,
+ waiting for a while and reading it again. If the value
+ was preserved, there is a device connected. */
+ grub_ata_regset (dev, GRUB_ATA_REG_SECTORS, 0x5A);
+ grub_ata_wait ();
+ grub_uint8_t sec = grub_ata_regget (dev, GRUB_ATA_REG_SECTORS);
+ grub_dprintf ("ata", "sectors=0x%x\n", sec);
+ if (sec != 0x5A)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no device connected");
+
+ /* The above test may detect a second (slave) device
+ connected to a SATA controller which supports only one
+ (master) device. It is not safe to use the status register
+ READY bit to check for controller channel existence. Some
+ ATAPI commands (RESET, DIAGNOSTIC) may clear this bit. */
+
+ /* Use the IDENTIFY DEVICE command to query the device. */
+ return grub_ata_identify (dev);
+}
+
+static grub_err_t
+grub_ata_device_initialize (int port, int device, int addr, int addr2)
+{
+ struct grub_ata_device *dev;
+ struct grub_ata_device **devp;
+ grub_err_t err;
+
+ grub_dprintf ("ata", "detecting device %d,%d (0x%x, 0x%x)\n",
+ port, device, addr, addr2);
+
+ dev = grub_malloc (sizeof(*dev));
+ if (! dev)
+ return grub_errno;
+
+ /* Setup the device information. */
+ dev->port = port;
+ dev->device = device;
+ dev->ioaddress = addr + GRUB_MACHINE_PCI_IO_BASE;
+ dev->ioaddress2 = addr2 + GRUB_MACHINE_PCI_IO_BASE;
+ dev->present = 1;
+ dev->next = NULL;
+
+ /* Register the device. */
+ for (devp = &grub_ata_devices; *devp; devp = &(*devp)->next);
+ *devp = dev;
+
+ err = check_device (dev);
+ if (err)
+ grub_print_error ();
+
+ return 0;
+}
+
+static int NESTED_FUNC_ATTR
+grub_ata_pciinit (grub_pci_device_t dev,
+ grub_pci_id_t pciid)
+{
+ static int compat_use[2] = { 0 };
+ grub_pci_address_t addr;
+ grub_uint32_t class;
+ grub_uint32_t bar1;
+ grub_uint32_t bar2;
+ int rega;
+ int regb;
+ int i;
+ static int controller = 0;
+ int cs5536 = 0;
+ int nports = 2;
+
+ /* Read class. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class = grub_pci_read (addr);
+
+ /* AMD CS5536 Southbridge. */
+ if (pciid == GRUB_CS5536_PCIID)
+ {
+ cs5536 = 1;
+ nports = 1;
+ }
+
+ /* Check if this class ID matches that of a PCI IDE Controller. */
+ if (!cs5536 && (class >> 16 != 0x0101))
+ return 0;
+
+ for (i = 0; i < nports; i++)
+ {
+ /* Set to 0 when the channel operated in compatibility mode. */
+ int compat;
+
+ /* We don't support non-compatibility mode for CS5536. */
+ if (cs5536)
+ compat = 0;
+ else
+ compat = (class >> (8 + 2 * i)) & 1;
+
+ rega = 0;
+ regb = 0;
+
+ /* If the channel is in compatibility mode, just assign the
+ default registers. */
+ if (compat == 0 && !compat_use[i])
+ {
+ rega = grub_ata_ioaddress[i];
+ regb = grub_ata_ioaddress2[i];
+ compat_use[i] = 1;
+ }
+ else if (compat)
+ {
+ /* Read the BARs, which either contain a mmapped IO address
+ or the IO port address. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
+ + sizeof (grub_uint64_t) * i);
+ bar1 = grub_pci_read (addr);
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
+ + sizeof (grub_uint64_t) * i
+ + sizeof (grub_uint32_t));
+ bar2 = grub_pci_read (addr);
+
+ /* Check if the BARs describe an IO region. */
+ if ((bar1 & 1) && (bar2 & 1))
+ {
+ rega = bar1 & ~3;
+ regb = bar2 & ~3;
+ }
+ }
+
+ grub_dprintf ("ata",
+ "PCI dev (%d,%d,%d) compat=%d rega=0x%x regb=0x%x\n",
+ grub_pci_get_bus (dev), grub_pci_get_device (dev),
+ grub_pci_get_function (dev), compat, rega, regb);
+
+ if (rega && regb)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ grub_ata_device_initialize (controller * 2 + i, 0, rega, regb);
+
+ /* Most errors raised by grub_ata_device_initialize() are harmless.
+ They just indicate this particular drive is not responding, most
+ likely because it doesn't exist. We might want to ignore specific
+ error types here, instead of printing them. */
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ grub_ata_device_initialize (controller * 2 + i, 1, rega, regb);
+
+ /* Likewise. */
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+ }
+
+ controller++;
+
+ return 0;
+}
+
+static grub_err_t
+grub_ata_initialize (void)
+{
+ grub_pci_iterate (grub_ata_pciinit);
+ return 0;
+}
+
+static void
+grub_ata_setlba (struct grub_ata_device *dev, grub_disk_addr_t sector,
+ grub_size_t size)
+{
+ grub_ata_regset (dev, GRUB_ATA_REG_SECTORS, size);
+ grub_ata_regset (dev, GRUB_ATA_REG_LBALOW, sector & 0xFF);
+ grub_ata_regset (dev, GRUB_ATA_REG_LBAMID, (sector >> 8) & 0xFF);
+ grub_ata_regset (dev, GRUB_ATA_REG_LBAHIGH, (sector >> 16) & 0xFF);
+}
+
+static grub_err_t
+grub_ata_setaddress (struct grub_ata_device *dev,
+ grub_ata_addressing_t addressing,
+ grub_disk_addr_t sector,
+ grub_size_t size)
+{
+ switch (addressing)
+ {
+ case GRUB_ATA_CHS:
+ {
+ unsigned int cylinder;
+ unsigned int head;
+ unsigned int sect;
+
+ /* Calculate the sector, cylinder and head to use. */
+ sect = ((grub_uint32_t) sector % dev->sectors_per_track) + 1;
+ cylinder = (((grub_uint32_t) sector / dev->sectors_per_track)
+ / dev->heads);
+ head = ((grub_uint32_t) sector / dev->sectors_per_track) % dev->heads;
+
+ if (sect > dev->sectors_per_track
+ || cylinder > dev->cylinders
+ || head > dev->heads)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ "sector %d cannot be addressed "
+ "using CHS addressing", sector);
+
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, (dev->device << 4) | head);
+ if (grub_ata_check_ready (dev))
+ return grub_errno;
+
+ grub_ata_regset (dev, GRUB_ATA_REG_SECTNUM, sect);
+ grub_ata_regset (dev, GRUB_ATA_REG_CYLLSB, cylinder & 0xFF);
+ grub_ata_regset (dev, GRUB_ATA_REG_CYLMSB, cylinder >> 8);
+
+ break;
+ }
+
+ case GRUB_ATA_LBA:
+ if (size == 256)
+ size = 0;
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK,
+ 0xE0 | (dev->device << 4) | ((sector >> 24) & 0x0F));
+ if (grub_ata_check_ready (dev))
+ return grub_errno;
+
+ grub_ata_setlba (dev, sector, size);
+ break;
+
+ case GRUB_ATA_LBA48:
+ if (size == 65536)
+ size = 0;
+
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | (dev->device << 4));
+ if (grub_ata_check_ready (dev))
+ return grub_errno;
+
+ /* Set "Previous". */
+ grub_ata_setlba (dev, sector >> 24, size >> 8);
+ /* Set "Current". */
+ grub_ata_setlba (dev, sector, size);
+
+ break;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf, int rw)
+{
+ struct grub_ata_device *dev = (struct grub_ata_device *) disk->data;
+
+ grub_dprintf("ata", "grub_ata_readwrite (size=%llu, rw=%d)\n", (unsigned long long) size, rw);
+
+ grub_ata_addressing_t addressing = dev->addr;
+ grub_size_t batch;
+ int cmd, cmd_write;
+
+ if (addressing == GRUB_ATA_LBA48 && ((sector + size) >> 28) != 0)
+ {
+ batch = 65536;
+ cmd = GRUB_ATA_CMD_READ_SECTORS_EXT;
+ cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_EXT;
+ }
+ else
+ {
+ if (addressing == GRUB_ATA_LBA48)
+ addressing = GRUB_ATA_LBA;
+ batch = 256;
+ cmd = GRUB_ATA_CMD_READ_SECTORS;
+ cmd_write = GRUB_ATA_CMD_WRITE_SECTORS;
+ }
+
+ grub_size_t nsectors = 0;
+ while (nsectors < size)
+ {
+ if (size - nsectors < batch)
+ batch = size - nsectors;
+
+ grub_dprintf("ata", "rw=%d, sector=%llu, batch=%llu\n", rw, (unsigned long long) sector, (unsigned long long) batch);
+
+ /* Send read/write command. */
+ if (grub_ata_setaddress (dev, addressing, sector, batch))
+ return grub_errno;
+
+ grub_ata_regset (dev, GRUB_ATA_REG_CMD, (! rw ? cmd : cmd_write));
+
+ unsigned sect;
+ for (sect = 0; sect < batch; sect++)
+ {
+ /* Wait for !BSY, DRQ. */
+ if (grub_ata_wait_drq (dev, rw, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ /* Transfer data. */
+ if (! rw)
+ grub_ata_pio_read (dev, buf, GRUB_DISK_SECTOR_SIZE);
+ else
+ grub_ata_pio_write (dev, buf, GRUB_DISK_SECTOR_SIZE);
+
+ buf += GRUB_DISK_SECTOR_SIZE;
+ }
+
+ if (rw)
+ {
+ /* Check for write error. */
+ if (grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ if (grub_ata_regget (dev, GRUB_ATA_REG_STATUS)
+ & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
+ }
+
+ sector += batch;
+ nsectors += batch;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+
+
+static int
+grub_ata_iterate (int (*hook) (const char *name))
+{
+ struct grub_ata_device *dev;
+
+ for (dev = grub_ata_devices; dev; dev = dev->next)
+ {
+ char devname[10];
+ grub_err_t err;
+
+ err = check_device (dev);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ if (dev->atapi)
+ continue;
+
+ grub_snprintf (devname, sizeof (devname),
+ "ata%d", dev->port * 2 + dev->device);
+
+ if (hook (devname))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_ata_open (const char *name, grub_disk_t disk)
+{
+ struct grub_ata_device *dev;
+ grub_err_t err;
+
+ for (dev = grub_ata_devices; dev; dev = dev->next)
+ {
+ char devname[10];
+ grub_snprintf (devname, sizeof (devname),
+ "ata%d", dev->port * 2 + dev->device);
+ if (grub_strcmp (name, devname) == 0)
+ break;
+ }
+
+ if (! dev)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+
+ if (dev->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
+
+ err = check_device (dev);
+
+ if (err)
+ return err;
+
+ disk->total_sectors = dev->size;
+
+ disk->id = (unsigned long) dev;
+
+ disk->data = dev;
+
+ return 0;
+}
+
+static void
+grub_ata_close (grub_disk_t disk __attribute__((unused)))
+{
+
+}
+
+static grub_err_t
+grub_ata_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ return grub_ata_readwrite (disk, sector, size, buf, 0);
+}
+
+static grub_err_t
+grub_ata_write (grub_disk_t disk,
+ grub_disk_addr_t sector,
+ grub_size_t size,
+ const char *buf)
+{
+ return grub_ata_readwrite (disk, sector, size, (char *) buf, 1);
+}
+
+static struct grub_disk_dev grub_atadisk_dev =
+ {
+ .name = "ATA",
+ .id = GRUB_DISK_DEVICE_ATA_ID,
+ .iterate = grub_ata_iterate,
+ .open = grub_ata_open,
+ .close = grub_ata_close,
+ .read = grub_ata_read,
+ .write = grub_ata_write,
+ .next = 0
+ };
+
+
+
+/* ATAPI code. */
+
+static int
+grub_atapi_iterate (int (*hook) (int bus, int luns))
+{
+ struct grub_ata_device *dev;
+
+ for (dev = grub_ata_devices; dev; dev = dev->next)
+ {
+ grub_err_t err;
+
+ err = check_device (dev);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ if (! dev->atapi)
+ continue;
+
+ if (hook (dev->port * 2 + dev->device, 1))
+ return 1;
+ }
+
+ return 0;
+
+}
+
+static grub_err_t
+grub_atapi_read (struct grub_scsi *scsi,
+ grub_size_t cmdsize __attribute__((unused)),
+ char *cmd, grub_size_t size, char *buf)
+{
+ struct grub_ata_device *dev = (struct grub_ata_device *) scsi->data;
+
+ grub_dprintf("ata", "grub_atapi_read (size=%llu)\n", (unsigned long long) size);
+
+ if (grub_atapi_packet (dev, cmd, size))
+ return grub_errno;
+
+ grub_size_t nread = 0;
+ while (nread < size)
+ {
+ /* Wait for !BSY, DRQ, I/O, !C/D. */
+ if (grub_atapi_wait_drq (dev, GRUB_ATAPI_IREASON_DATA_IN, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ /* Get byte count for this DRQ assertion. */
+ unsigned cnt = grub_ata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
+ | grub_ata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
+ grub_dprintf("ata", "DRQ count=%u\n", cnt);
+
+ /* Count of last transfer may be uneven. */
+ if (! (0 < cnt && cnt <= size - nread && (! (cnt & 1) || cnt == size - nread)))
+ return grub_error (GRUB_ERR_READ_ERROR, "invalid ATAPI transfer count");
+
+ /* Read the data. */
+ grub_ata_pio_read (dev, buf + nread, cnt);
+
+ if (cnt & 1)
+ buf[nread + cnt - 1] = (char) grub_le_to_cpu16 (grub_inw (dev->ioaddress + GRUB_ATA_REG_DATA));
+
+ nread += cnt;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_atapi_write (struct grub_scsi *scsi __attribute__((unused)),
+ grub_size_t cmdsize __attribute__((unused)),
+ char *cmd __attribute__((unused)),
+ grub_size_t size __attribute__((unused)),
+ char *buf __attribute__((unused)))
+{
+ // XXX: scsi.mod does not use write yet.
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "ATAPI write not implemented");
+}
+
+static grub_err_t
+grub_atapi_open (int devnum, struct grub_scsi *scsi)
+{
+ struct grub_ata_device *dev;
+ struct grub_ata_device *devfnd = 0;
+ grub_err_t err;
+
+ for (dev = grub_ata_devices; dev; dev = dev->next)
+ {
+ if (dev->port * 2 + dev->device == devnum)
+ {
+ devfnd = dev;
+ break;
+ }
+ }
+
+ grub_dprintf ("ata", "opening ATAPI dev `ata%d'\n", devnum);
+
+ if (! devfnd)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
+
+ err = check_device (devfnd);
+ if (err)
+ return err;
+
+ if (! devfnd->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
+
+ scsi->data = devfnd;
+
+ return GRUB_ERR_NONE;
+}
+
+
+static struct grub_scsi_dev grub_atapi_dev =
+ {
+ .name = "ata",
+ .id = GRUB_SCSI_SUBSYSTEM_ATAPI,
+ .iterate = grub_atapi_iterate,
+ .open = grub_atapi_open,
+ .read = grub_atapi_read,
+ .write = grub_atapi_write
+ };
+
+
+
+GRUB_MOD_INIT(ata)
+{
+ /* To prevent two drivers operating on the same disks. */
+ grub_disk_firmware_is_tainted = 1;
+ if (grub_disk_firmware_fini)
+ {
+ grub_disk_firmware_fini ();
+ grub_disk_firmware_fini = NULL;
+ }
+
+ /* ATA initialization. */
+ grub_ata_initialize ();
+
+ grub_disk_dev_register (&grub_atadisk_dev);
+
+ /* ATAPI devices are handled by scsi.mod. */
+ grub_scsi_dev_register (&grub_atapi_dev);
+}
+
+GRUB_MOD_FINI(ata)
+{
+ grub_scsi_dev_unregister (&grub_atapi_dev);
+ grub_disk_dev_unregister (&grub_atadisk_dev);
+}
diff --git a/grub-core/disk/ata_pthru.c b/grub-core/disk/ata_pthru.c
new file mode 100644
index 0000000..eb9cb5f
--- /dev/null
+++ b/grub-core/disk/ata_pthru.c
@@ -0,0 +1,108 @@
+/* ata_pthru.c - ATA pass through for ata.mod. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/ata.h>
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* ATA pass through support, used by hdparm.mod. */
+static grub_err_t
+grub_ata_pass_through (grub_disk_t disk,
+ struct grub_disk_ata_pass_through_parms *parms)
+{
+ if (disk->dev->id != GRUB_DISK_DEVICE_ATA_ID)
+ return grub_error (GRUB_ERR_BAD_DEVICE,
+ "device not accessed via ata.mod");
+
+ struct grub_ata_device *dev = (struct grub_ata_device *) disk->data;
+
+ if (! (parms->size == 0 || parms->size == GRUB_DISK_SECTOR_SIZE))
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "ATA multi-sector read and DATA OUT not implemented");
+
+ grub_dprintf ("ata", "ata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
+ parms->taskfile[GRUB_ATA_REG_CMD],
+ parms->taskfile[GRUB_ATA_REG_FEATURES],
+ parms->taskfile[GRUB_ATA_REG_SECTORS]);
+ grub_dprintf ("ata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%d\n",
+ parms->taskfile[GRUB_ATA_REG_LBAHIGH],
+ parms->taskfile[GRUB_ATA_REG_LBAMID],
+ parms->taskfile[GRUB_ATA_REG_LBALOW], parms->size);
+
+ /* Set registers. */
+ grub_ata_regset (dev, GRUB_ATA_REG_DISK, 0xE0 | dev->device << 4
+ | (parms->taskfile[GRUB_ATA_REG_DISK] & 0xf));
+ if (grub_ata_check_ready (dev))
+ return grub_errno;
+
+ int i;
+ for (i = GRUB_ATA_REG_FEATURES; i <= GRUB_ATA_REG_LBAHIGH; i++)
+ grub_ata_regset (dev, i, parms->taskfile[i]);
+
+ /* Start command. */
+ grub_ata_regset (dev, GRUB_ATA_REG_CMD, parms->taskfile[GRUB_ATA_REG_CMD]);
+
+ /* Wait for !BSY. */
+ if (grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ /* Check status. */
+ grub_int8_t sts = grub_ata_regget (dev, GRUB_ATA_REG_STATUS);
+ grub_dprintf ("ata", "status=0x%x\n", sts);
+
+ /* Transfer data. */
+ if ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_DRQ)
+ {
+ if (parms->size != GRUB_DISK_SECTOR_SIZE)
+ return grub_error (GRUB_ERR_READ_ERROR, "DRQ unexpected");
+ grub_ata_pio_read (dev, parms->buffer, GRUB_DISK_SECTOR_SIZE);
+ }
+
+ /* Return registers. */
+ for (i = GRUB_ATA_REG_ERROR; i <= GRUB_ATA_REG_STATUS; i++)
+ parms->taskfile[i] = grub_ata_regget (dev, i);
+
+ grub_dprintf ("ata", "status=0x%x, error=0x%x, sectors=0x%x\n",
+ parms->taskfile[GRUB_ATA_REG_STATUS],
+ parms->taskfile[GRUB_ATA_REG_ERROR],
+ parms->taskfile[GRUB_ATA_REG_SECTORS]);
+
+ if (parms->taskfile[GRUB_ATA_REG_STATUS]
+ & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ return grub_error (GRUB_ERR_READ_ERROR, "ATA passthrough failed");
+
+ return GRUB_ERR_NONE;
+}
+
+
+
+GRUB_MOD_INIT(ata_pthru)
+{
+ /* Register ATA pass through function. */
+ grub_disk_ata_pass_through = grub_ata_pass_through;
+}
+
+GRUB_MOD_FINI(ata_pthru)
+{
+ if (grub_disk_ata_pass_through == grub_ata_pass_through)
+ grub_disk_ata_pass_through = NULL;
+}
diff --git a/grub-core/disk/dmraid_nvidia.c b/grub-core/disk/dmraid_nvidia.c
new file mode 100644
index 0000000..154193e
--- /dev/null
+++ b/grub-core/disk/dmraid_nvidia.c
@@ -0,0 +1,171 @@
+/* dmraid_nvidia.c - module to handle Nvidia fakeraid. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define NV_SIGNATURES 4
+
+#define NV_IDLE 0
+#define NV_SCDB_INIT_RAID 2
+#define NV_SCDB_REBUILD_RAID 3
+#define NV_SCDB_UPGRADE_RAID 4
+#define NV_SCDB_SYNC_RAID 5
+
+#define NV_LEVEL_UNKNOWN 0x00
+#define NV_LEVEL_JBOD 0xFF
+#define NV_LEVEL_0 0x80
+#define NV_LEVEL_1 0x81
+#define NV_LEVEL_3 0x83
+#define NV_LEVEL_5 0x85
+#define NV_LEVEL_10 0x8a
+#define NV_LEVEL_1_0 0x8180
+
+#define NV_ARRAY_FLAG_BOOT 1 /* BIOS use only. */
+#define NV_ARRAY_FLAG_ERROR 2 /* Degraded or offline. */
+#define NV_ARRAY_FLAG_PARITY_VALID 4 /* RAID-3/5 parity valid. */
+
+struct grub_nv_array
+{
+ grub_uint32_t version;
+ grub_uint32_t signature[NV_SIGNATURES];
+ grub_uint8_t raid_job_code;
+ grub_uint8_t stripe_width;
+ grub_uint8_t total_volumes;
+ grub_uint8_t original_width;
+ grub_uint32_t raid_level;
+ grub_uint32_t stripe_block_size;
+ grub_uint32_t stripe_block_size_bytes;
+ grub_uint32_t stripe_block_size_log2;
+ grub_uint32_t stripe_mask;
+ grub_uint32_t stripe_size;
+ grub_uint32_t stripe_size_bytes;
+ grub_uint32_t raid_job_mask;
+ grub_uint32_t original_capacity;
+ grub_uint32_t flags;
+};
+
+#define NV_ID_LEN 8
+#define NV_ID_STRING "NVIDIA"
+#define NV_VERSION 100
+
+#define NV_PRODID_LEN 16
+#define NV_PRODREV_LEN 4
+
+struct grub_nv_super
+{
+ char vendor[NV_ID_LEN]; /* 0x00 - 0x07 ID string. */
+ grub_uint32_t size; /* 0x08 - 0x0B Size of metadata in dwords. */
+ grub_uint32_t chksum; /* 0x0C - 0x0F Checksum of this struct. */
+ grub_uint16_t version; /* 0x10 - 0x11 NV version. */
+ grub_uint8_t unit_number; /* 0x12 Disk index in array. */
+ grub_uint8_t reserved; /* 0x13. */
+ grub_uint32_t capacity; /* 0x14 - 0x17 Array capacity in sectors. */
+ grub_uint32_t sector_size; /* 0x18 - 0x1B Sector size. */
+ char prodid[NV_PRODID_LEN]; /* 0x1C - 0x2B Array product ID. */
+ char prodrev[NV_PRODREV_LEN]; /* 0x2C - 0x2F Array product revision */
+ grub_uint32_t unit_flags; /* 0x30 - 0x33 Flags for this disk */
+ struct grub_nv_array array; /* Array information */
+} __attribute__ ((packed));
+
+static grub_err_t
+grub_dmraid_nv_detect (grub_disk_t disk, struct grub_raid_array *array,
+ grub_disk_addr_t *start_sector)
+{
+ grub_disk_addr_t sector;
+ struct grub_nv_super sb;
+
+ if (disk->partition)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "skip partition");
+
+ sector = grub_disk_get_size (disk) - 2;
+
+ if (grub_disk_read (disk, sector, 0, sizeof (sb), &sb))
+ return grub_errno;
+
+ if (grub_memcmp (sb.vendor, NV_ID_STRING, 6))
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "not raid");
+
+ if (sb.version != NV_VERSION)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unknown version: %d.%d", sb.version);
+
+ switch (sb.array.raid_level)
+ {
+ case NV_LEVEL_0:
+ array->level = 0;
+ array->disk_size = sb.capacity / sb.array.total_volumes;
+ break;
+
+ case NV_LEVEL_1:
+ array->level = 1;
+ array->disk_size = sb.capacity;
+ break;
+
+ case NV_LEVEL_5:
+ array->level = 5;
+ array->layout = GRUB_RAID_LAYOUT_LEFT_ASYMMETRIC;
+ array->disk_size = sb.capacity / (sb.array.total_volumes - 1);
+ break;
+
+ default:
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level: %d", sb.array.raid_level);
+ }
+
+ array->name = NULL;
+ array->number = 0;
+ array->total_devs = sb.array.total_volumes;
+ array->chunk_size = sb.array.stripe_block_size;
+ array->index = sb.unit_number;
+ array->uuid_len = sizeof (sb.array.signature);
+ array->uuid = grub_malloc (sizeof (sb.array.signature));
+ if (! array->uuid)
+ return grub_errno;
+
+ grub_memcpy (array->uuid, (char *) &sb.array.signature,
+ sizeof (sb.array.signature));
+
+ *start_sector = 0;
+
+ return 0;
+}
+
+static struct grub_raid grub_dmraid_nv_dev =
+{
+ .name = "dmraid_nv",
+ .detect = grub_dmraid_nv_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT(dm_nv)
+{
+ grub_raid_register (&grub_dmraid_nv_dev);
+}
+
+GRUB_MOD_FINI(dm_nv)
+{
+ grub_raid_unregister (&grub_dmraid_nv_dev);
+}
diff --git a/grub-core/disk/efi/efidisk.c b/grub-core/disk/efi/efidisk.c
new file mode 100644
index 0000000..08094fa
--- /dev/null
+++ b/grub-core/disk/efi/efidisk.c
@@ -0,0 +1,850 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/disk.h>
+#include <grub/partition.h>
+#include <grub/mm.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/err.h>
+#include <grub/term.h>
+#include <grub/efi/api.h>
+#include <grub/efi/efi.h>
+#include <grub/efi/disk.h>
+
+struct grub_efidisk_data
+{
+ grub_efi_handle_t handle;
+ grub_efi_device_path_t *device_path;
+ grub_efi_device_path_t *last_device_path;
+ grub_efi_block_io_t *block_io;
+ grub_efi_disk_io_t *disk_io;
+ struct grub_efidisk_data *next;
+};
+
+/* GUIDs. */
+static grub_efi_guid_t disk_io_guid = GRUB_EFI_DISK_IO_GUID;
+static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID;
+
+static struct grub_efidisk_data *fd_devices;
+static struct grub_efidisk_data *hd_devices;
+static struct grub_efidisk_data *cd_devices;
+
+/* Duplicate a device path. */
+static grub_efi_device_path_t *
+duplicate_device_path (const grub_efi_device_path_t *dp)
+{
+ grub_efi_device_path_t *p;
+ grub_size_t total_size = 0;
+
+ for (p = (grub_efi_device_path_t *) dp;
+ ;
+ p = GRUB_EFI_NEXT_DEVICE_PATH (p))
+ {
+ total_size += GRUB_EFI_DEVICE_PATH_LENGTH (p);
+ if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (p))
+ break;
+ }
+
+ p = grub_malloc (total_size);
+ if (! p)
+ return 0;
+
+ grub_memcpy (p, dp, total_size);
+ return p;
+}
+
+/* Return the device path node right before the end node. */
+static grub_efi_device_path_t *
+find_last_device_path (const grub_efi_device_path_t *dp)
+{
+ grub_efi_device_path_t *next, *p;
+
+ if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp))
+ return 0;
+
+ for (p = (grub_efi_device_path_t *) dp, next = GRUB_EFI_NEXT_DEVICE_PATH (p);
+ ! GRUB_EFI_END_ENTIRE_DEVICE_PATH (next);
+ p = next, next = GRUB_EFI_NEXT_DEVICE_PATH (next))
+ ;
+
+ return p;
+}
+
+/* Compare device paths. */
+static int
+compare_device_paths (const grub_efi_device_path_t *dp1,
+ const grub_efi_device_path_t *dp2)
+{
+ if (! dp1 || ! dp2)
+ /* Return non-zero. */
+ return 1;
+
+ while (1)
+ {
+ grub_efi_uint8_t type1, type2;
+ grub_efi_uint8_t subtype1, subtype2;
+ grub_efi_uint16_t len1, len2;
+ int ret;
+
+ type1 = GRUB_EFI_DEVICE_PATH_TYPE (dp1);
+ type2 = GRUB_EFI_DEVICE_PATH_TYPE (dp2);
+
+ if (type1 != type2)
+ return (int) type2 - (int) type1;
+
+ subtype1 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp1);
+ subtype2 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp2);
+
+ if (subtype1 != subtype2)
+ return (int) subtype1 - (int) subtype2;
+
+ len1 = GRUB_EFI_DEVICE_PATH_LENGTH (dp1);
+ len2 = GRUB_EFI_DEVICE_PATH_LENGTH (dp2);
+
+ if (len1 != len2)
+ return (int) len1 - (int) len2;
+
+ ret = grub_memcmp (dp1, dp2, len1);
+ if (ret != 0)
+ return ret;
+
+ if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp1))
+ break;
+
+ dp1 = (grub_efi_device_path_t *) ((char *) dp1 + len1);
+ dp2 = (grub_efi_device_path_t *) ((char *) dp2 + len2);
+ }
+
+ return 0;
+}
+
+static struct grub_efidisk_data *
+make_devices (void)
+{
+ grub_efi_uintn_t num_handles;
+ grub_efi_handle_t *handles;
+ grub_efi_handle_t *handle;
+ struct grub_efidisk_data *devices = 0;
+
+ /* Find handles which support the disk io interface. */
+ handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &disk_io_guid,
+ 0, &num_handles);
+ if (! handles)
+ return 0;
+
+ /* Make a linked list of devices. */
+ for (handle = handles; num_handles--; handle++)
+ {
+ grub_efi_device_path_t *dp;
+ grub_efi_device_path_t *ldp;
+ struct grub_efidisk_data *d;
+ grub_efi_block_io_t *bio;
+ grub_efi_disk_io_t *dio;
+
+ dp = grub_efi_get_device_path (*handle);
+ if (! dp)
+ continue;
+
+ ldp = find_last_device_path (dp);
+ if (! ldp)
+ /* This is empty. Why? */
+ continue;
+
+ bio = grub_efi_open_protocol (*handle, &block_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ dio = grub_efi_open_protocol (*handle, &disk_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
+ if (! bio || ! dio)
+ /* This should not happen... Why? */
+ continue;
+
+ d = grub_malloc (sizeof (*d));
+ if (! d)
+ {
+ /* Uggh. */
+ grub_free (handles);
+ return 0;
+ }
+
+ d->handle = *handle;
+ d->device_path = dp;
+ d->last_device_path = ldp;
+ d->block_io = bio;
+ d->disk_io = dio;
+ d->next = devices;
+ devices = d;
+ }
+
+ grub_free (handles);
+
+ return devices;
+}
+
+/* Find the parent device. */
+static struct grub_efidisk_data *
+find_parent_device (struct grub_efidisk_data *devices,
+ struct grub_efidisk_data *d)
+{
+ grub_efi_device_path_t *dp, *ldp;
+ struct grub_efidisk_data *parent;
+
+ dp = duplicate_device_path (d->device_path);
+ if (! dp)
+ return 0;
+
+ ldp = find_last_device_path (dp);
+ ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ ldp->length[0] = sizeof (*ldp);
+ ldp->length[1] = 0;
+
+ for (parent = devices; parent; parent = parent->next)
+ {
+ /* Ignore itself. */
+ if (parent == d)
+ continue;
+
+ if (compare_device_paths (parent->device_path, dp) == 0)
+ {
+ /* Found. */
+ if (! parent->last_device_path)
+ parent = 0;
+
+ break;
+ }
+ }
+
+ grub_free (dp);
+ return parent;
+}
+
+static int
+iterate_child_devices (struct grub_efidisk_data *devices,
+ struct grub_efidisk_data *d,
+ int (*hook) (struct grub_efidisk_data *child))
+{
+ struct grub_efidisk_data *p;
+
+ for (p = devices; p; p = p->next)
+ {
+ grub_efi_device_path_t *dp, *ldp;
+
+ dp = duplicate_device_path (p->device_path);
+ if (! dp)
+ return 0;
+
+ ldp = find_last_device_path (dp);
+ ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ ldp->length[0] = sizeof (*ldp);
+ ldp->length[1] = 0;
+
+ if (compare_device_paths (dp, d->device_path) == 0)
+ if (hook (p))
+ {
+ grub_free (dp);
+ return 1;
+ }
+
+ grub_free (dp);
+ }
+
+ return 0;
+}
+
+/* Add a device into a list of devices in an ascending order. */
+static void
+add_device (struct grub_efidisk_data **devices, struct grub_efidisk_data *d)
+{
+ struct grub_efidisk_data **p;
+ struct grub_efidisk_data *n;
+
+ for (p = devices; *p; p = &((*p)->next))
+ {
+ int ret;
+
+ ret = compare_device_paths (find_last_device_path ((*p)->device_path),
+ find_last_device_path (d->device_path));
+ if (ret == 0)
+ ret = compare_device_paths ((*p)->device_path,
+ d->device_path);
+ if (ret == 0)
+ return;
+ else if (ret > 0)
+ break;
+ }
+
+ n = grub_malloc (sizeof (*n));
+ if (! n)
+ return;
+
+ grub_memcpy (n, d, sizeof (*n));
+ n->next = (*p);
+ (*p) = n;
+}
+
+/* Name the devices. */
+static void
+name_devices (struct grub_efidisk_data *devices)
+{
+ struct grub_efidisk_data *d;
+
+ /* First, identify devices by media device paths. */
+ for (d = devices; d; d = d->next)
+ {
+ grub_efi_device_path_t *dp;
+
+ dp = d->last_device_path;
+ if (! dp)
+ continue;
+
+ if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE)
+ {
+ int is_hard_drive = 0;
+
+ switch (GRUB_EFI_DEVICE_PATH_SUBTYPE (dp))
+ {
+ case GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE:
+ is_hard_drive = 1;
+ /* Fall through by intention. */
+ case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE:
+ {
+ struct grub_efidisk_data *parent;
+
+ parent = find_parent_device (devices, d);
+ if (parent)
+ {
+ if (is_hard_drive)
+ {
+#if 0
+ grub_printf ("adding a hard drive by a partition: ");
+ grub_print_device_path (parent->device_path);
+#endif
+ add_device (&hd_devices, parent);
+ }
+ else
+ {
+#if 0
+ grub_printf ("adding a cdrom by a partition: ");
+ grub_print_device_path (parent->device_path);
+#endif
+ add_device (&cd_devices, parent);
+ }
+
+ /* Mark the parent as used. */
+ parent->last_device_path = 0;
+ }
+ }
+ /* Mark itself as used. */
+ d->last_device_path = 0;
+ break;
+
+ default:
+ /* For now, ignore the others. */
+ break;
+ }
+ }
+ }
+
+ /* Let's see what can be added more. */
+ for (d = devices; d; d = d->next)
+ {
+ grub_efi_device_path_t *dp;
+ grub_efi_block_io_media_t *m;
+
+ dp = d->last_device_path;
+ if (! dp)
+ continue;
+
+ m = d->block_io->media;
+ if (m->logical_partition)
+ {
+ /* Only one partition in a non-media device. Assume that this
+ is a floppy drive. */
+#if 0
+ grub_printf ("adding a floppy by guessing: ");
+ grub_print_device_path (d->device_path);
+#endif
+ add_device (&fd_devices, d);
+ }
+ else if (m->read_only && m->block_size > GRUB_DISK_SECTOR_SIZE)
+ {
+ /* This check is too heuristic, but assume that this is a
+ CDROM drive. */
+#if 0
+ grub_printf ("adding a cdrom by guessing: ");
+ grub_print_device_path (d->device_path);
+#endif
+ add_device (&cd_devices, d);
+ }
+ else
+ {
+ /* The default is a hard drive. */
+#if 0
+ grub_printf ("adding a hard drive by guessing: ");
+ grub_print_device_path (d->device_path);
+#endif
+ add_device (&hd_devices, d);
+ }
+ }
+}
+
+static void
+free_devices (struct grub_efidisk_data *devices)
+{
+ struct grub_efidisk_data *p, *q;
+
+ for (p = devices; p; p = q)
+ {
+ q = p->next;
+ grub_free (p);
+ }
+}
+
+/* Enumerate all disks to name devices. */
+static void
+enumerate_disks (void)
+{
+ struct grub_efidisk_data *devices;
+
+ devices = make_devices ();
+ if (! devices)
+ return;
+
+ name_devices (devices);
+ free_devices (devices);
+}
+
+static int
+grub_efidisk_iterate (int (*hook) (const char *name))
+{
+ struct grub_efidisk_data *d;
+ char buf[16];
+ int count;
+
+ for (d = fd_devices, count = 0; d; d = d->next, count++)
+ {
+ grub_snprintf (buf, sizeof (buf), "fd%d", count);
+ grub_dprintf ("efidisk", "iterating %s\n", buf);
+ if (hook (buf))
+ return 1;
+ }
+
+ for (d = hd_devices, count = 0; d; d = d->next, count++)
+ {
+ grub_snprintf (buf, sizeof (buf), "hd%d", count);
+ grub_dprintf ("efidisk", "iterating %s\n", buf);
+ if (hook (buf))
+ return 1;
+ }
+
+ for (d = cd_devices, count = 0; d; d = d->next, count++)
+ {
+ grub_snprintf (buf, sizeof (buf), "cd%d", count);
+ grub_dprintf ("efidisk", "iterating %s\n", buf);
+ if (hook (buf))
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+get_drive_number (const char *name)
+{
+ unsigned long drive;
+
+ if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd')
+ goto fail;
+
+ drive = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+
+ return (int) drive ;
+
+ fail:
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk");
+ return -1;
+}
+
+static struct grub_efidisk_data *
+get_device (struct grub_efidisk_data *devices, int num)
+{
+ struct grub_efidisk_data *d;
+
+ for (d = devices; d && num; d = d->next, num--)
+ ;
+
+ if (num == 0)
+ return d;
+
+ return 0;
+}
+
+static grub_err_t
+grub_efidisk_open (const char *name, struct grub_disk *disk)
+{
+ int num;
+ struct grub_efidisk_data *d = 0;
+ grub_efi_block_io_media_t *m;
+
+ grub_dprintf ("efidisk", "opening %s\n", name);
+
+ num = get_drive_number (name);
+ if (num < 0)
+ return grub_errno;
+
+ switch (name[0])
+ {
+ case 'f':
+ d = get_device (fd_devices, num);
+ break;
+ case 'c':
+ d = get_device (cd_devices, num);
+ break;
+ case 'h':
+ d = get_device (hd_devices, num);
+ break;
+ default:
+ /* Never reach here. */
+ break;
+ }
+
+ if (! d)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device");
+
+ disk->id = ((num << 8) | name[0]);
+ m = d->block_io->media;
+ /* FIXME: Probably it is better to store the block size in the disk,
+ and total sectors should be replaced with total blocks. */
+ grub_dprintf ("efidisk", "m = %p, last block = %llx, block size = %x\n",
+ m, (unsigned long long) m->last_block, m->block_size);
+ disk->total_sectors = (m->last_block
+ * (m->block_size >> GRUB_DISK_SECTOR_BITS));
+ disk->data = d;
+
+ grub_dprintf ("efidisk", "opening %s succeeded\n", name);
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_efidisk_close (struct grub_disk *disk __attribute__ ((unused)))
+{
+ /* EFI disks do not allocate extra memory, so nothing to do here. */
+ grub_dprintf ("efidisk", "closing %s\n", disk->name);
+}
+
+static grub_err_t
+grub_efidisk_read (struct grub_disk *disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ /* For now, use the disk io interface rather than the block io's. */
+ struct grub_efidisk_data *d;
+ grub_efi_disk_io_t *dio;
+ grub_efi_block_io_t *bio;
+ grub_efi_status_t status;
+
+ d = disk->data;
+ dio = d->disk_io;
+ bio = d->block_io;
+
+ grub_dprintf ("efidisk",
+ "reading 0x%lx sectors at the sector 0x%llx from %s\n",
+ (unsigned long) size, (unsigned long long) sector, disk->name);
+
+ status = efi_call_5 (dio->read, dio, bio->media->media_id,
+ (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS,
+ (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS,
+ buf);
+ if (status != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_READ_ERROR, "efidisk read error");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_efidisk_write (struct grub_disk *disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ /* For now, use the disk io interface rather than the block io's. */
+ struct grub_efidisk_data *d;
+ grub_efi_disk_io_t *dio;
+ grub_efi_block_io_t *bio;
+ grub_efi_status_t status;
+
+ d = disk->data;
+ dio = d->disk_io;
+ bio = d->block_io;
+
+ grub_dprintf ("efidisk",
+ "writing 0x%lx sectors at the sector 0x%llx to %s\n",
+ (unsigned long) size, (unsigned long long) sector, disk->name);
+
+ status = efi_call_5 (dio->write, dio, bio->media->media_id,
+ (grub_efi_uint64_t) sector << GRUB_DISK_SECTOR_BITS,
+ (grub_efi_uintn_t) size << GRUB_DISK_SECTOR_BITS,
+ (void *) buf);
+ if (status != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_WRITE_ERROR, "efidisk write error");
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_disk_dev grub_efidisk_dev =
+ {
+ .name = "efidisk",
+ .id = GRUB_DISK_DEVICE_EFIDISK_ID,
+ .iterate = grub_efidisk_iterate,
+ .open = grub_efidisk_open,
+ .close = grub_efidisk_close,
+ .read = grub_efidisk_read,
+ .write = grub_efidisk_write,
+ .next = 0
+ };
+
+void
+grub_efidisk_init (void)
+{
+ enumerate_disks ();
+ grub_disk_dev_register (&grub_efidisk_dev);
+}
+
+void
+grub_efidisk_fini (void)
+{
+ free_devices (fd_devices);
+ free_devices (hd_devices);
+ free_devices (cd_devices);
+ grub_disk_dev_unregister (&grub_efidisk_dev);
+}
+
+/* Some utility functions to map GRUB devices with EFI devices. */
+grub_efi_handle_t
+grub_efidisk_get_device_handle (grub_disk_t disk)
+{
+ struct grub_efidisk_data *d;
+ char type;
+
+ if (disk->dev->id != GRUB_DISK_DEVICE_EFIDISK_ID)
+ return 0;
+
+ d = disk->data;
+ type = disk->name[0];
+
+ switch (type)
+ {
+ case 'f':
+ /* This is the simplest case. */
+ return d->handle;
+
+ case 'c':
+ /* FIXME: probably this is not correct. */
+ return d->handle;
+
+ case 'h':
+ /* If this is the whole disk, just return its own data. */
+ if (! disk->partition)
+ return d->handle;
+
+ /* Otherwise, we must query the corresponding device to the firmware. */
+ {
+ struct grub_efidisk_data *devices;
+ grub_efi_handle_t handle = 0;
+ auto int find_partition (struct grub_efidisk_data *c);
+
+ int find_partition (struct grub_efidisk_data *c)
+ {
+ grub_efi_hard_drive_device_path_t hd;
+
+ grub_memcpy (&hd, c->last_device_path, sizeof (hd));
+
+ if ((GRUB_EFI_DEVICE_PATH_TYPE (c->last_device_path)
+ == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE)
+ && (GRUB_EFI_DEVICE_PATH_SUBTYPE (c->last_device_path)
+ == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)
+ && (grub_partition_get_start (disk->partition)
+ == hd.partition_start)
+ && (grub_partition_get_len (disk->partition)
+ == hd.partition_size))
+ {
+ handle = c->handle;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ devices = make_devices ();
+ iterate_child_devices (devices, d, find_partition);
+ free_devices (devices);
+
+ if (handle != 0)
+ return handle;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+char *
+grub_efidisk_get_device_name (grub_efi_handle_t *handle)
+{
+ grub_efi_device_path_t *dp, *ldp;
+
+ dp = grub_efi_get_device_path (handle);
+ if (! dp)
+ return 0;
+
+ ldp = find_last_device_path (dp);
+ if (! ldp)
+ return 0;
+
+ if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
+ && (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp)
+ == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE))
+ {
+ /* This is a hard disk partition. */
+ grub_disk_t parent = 0;
+ grub_partition_t tpart = NULL;
+ char *device_name;
+ grub_efi_device_path_t *dup_dp, *dup_ldp;
+ grub_efi_hard_drive_device_path_t hd;
+ auto int find_parent_disk (const char *name);
+ auto int find_partition (grub_disk_t disk, const grub_partition_t part);
+
+ /* Find the disk which is the parent of a given hard disk partition. */
+ int find_parent_disk (const char *name)
+ {
+ grub_disk_t disk;
+
+ disk = grub_disk_open (name);
+ if (! disk)
+ return 1;
+
+ if (disk->dev->id == GRUB_DISK_DEVICE_EFIDISK_ID)
+ {
+ struct grub_efidisk_data *d;
+
+ d = disk->data;
+ if (compare_device_paths (d->device_path, dup_dp) == 0)
+ {
+ parent = disk;
+ return 1;
+ }
+ }
+
+ grub_disk_close (disk);
+ return 0;
+ }
+
+ /* Find the identical partition. */
+ int find_partition (grub_disk_t disk __attribute__ ((unused)),
+ const grub_partition_t part)
+ {
+ if (grub_partition_get_start (part) == hd.partition_start
+ && grub_partition_get_len (part) == hd.partition_size)
+ {
+ tpart = part;
+ return 1;
+ }
+
+ return 0;
+ }
+
+ /* It is necessary to duplicate the device path so that GRUB
+ can overwrite it. */
+ dup_dp = duplicate_device_path (dp);
+ if (! dup_dp)
+ return 0;
+
+ dup_ldp = find_last_device_path (dup_dp);
+ dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ dup_ldp->length[0] = sizeof (*dup_ldp);
+ dup_ldp->length[1] = 0;
+
+ grub_efidisk_iterate (find_parent_disk);
+ grub_free (dup_dp);
+
+ if (! parent)
+ return 0;
+
+ /* Find a partition which matches the hard drive device path. */
+ grub_memcpy (&hd, ldp, sizeof (hd));
+ grub_partition_iterate (parent, find_partition);
+
+ if (! tpart)
+ {
+ grub_disk_close (parent);
+ return 0;
+ }
+
+ {
+ char *partition_name = grub_partition_get_name (tpart);
+ device_name = grub_xasprintf ("%s,%s", parent->name, partition_name);
+ grub_free (partition_name);
+ }
+ grub_disk_close (parent);
+
+ return device_name;
+ }
+ else
+ {
+ /* This should be an entire disk. */
+ auto int find_disk (const char *name);
+ char *device_name = 0;
+
+ int find_disk (const char *name)
+ {
+ grub_disk_t disk;
+
+ disk = grub_disk_open (name);
+ if (! disk)
+ return 1;
+
+ if (disk->dev->id == GRUB_DISK_DEVICE_EFIDISK_ID)
+ {
+ struct grub_efidisk_data *d;
+
+ d = disk->data;
+ if (compare_device_paths (d->device_path, dp) == 0)
+ {
+ device_name = grub_strdup (disk->name);
+ grub_disk_close (disk);
+ return 1;
+ }
+ }
+
+ grub_disk_close (disk);
+ return 0;
+
+ }
+
+ grub_efidisk_iterate (find_disk);
+ return device_name;
+ }
+
+ return 0;
+}
diff --git a/grub-core/disk/host.c b/grub-core/disk/host.c
new file mode 100644
index 0000000..c519662
--- /dev/null
+++ b/grub-core/disk/host.c
@@ -0,0 +1,95 @@
+/* host.c - Dummy disk driver to provide access to the hosts filesystem */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2007 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/>.
+ */
+
+/* When using the disk, make a reference to this module. Otherwise
+ the user will end up with a useless module :-). */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/misc.h>
+
+int grub_disk_host_i_want_a_reference;
+
+static int
+grub_host_iterate (int (*hook) (const char *name))
+{
+ if (hook ("host"))
+ return 1;
+ return 0;
+}
+
+static grub_err_t
+grub_host_open (const char *name, grub_disk_t disk)
+{
+ if (grub_strcmp (name, "host"))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a host disk");
+
+ disk->total_sectors = 0;
+ disk->id = (unsigned long) "host";
+
+ disk->data = 0;
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_host_close (grub_disk_t disk __attribute((unused)))
+{
+}
+
+static grub_err_t
+grub_host_read (grub_disk_t disk __attribute((unused)),
+ grub_disk_addr_t sector __attribute((unused)),
+ grub_size_t size __attribute((unused)),
+ char *buf __attribute((unused)))
+{
+ return GRUB_ERR_OUT_OF_RANGE;
+}
+
+static grub_err_t
+grub_host_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_OUT_OF_RANGE;
+}
+
+static struct grub_disk_dev grub_host_dev =
+ {
+ /* The only important line in this file :-) */
+ .name = "host",
+ .id = GRUB_DISK_DEVICE_HOST_ID,
+ .iterate = grub_host_iterate,
+ .open = grub_host_open,
+ .close = grub_host_close,
+ .read = grub_host_read,
+ .write = grub_host_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(host)
+{
+ grub_disk_dev_register (&grub_host_dev);
+}
+
+GRUB_MOD_FINI(host)
+{
+ grub_disk_dev_unregister (&grub_host_dev);
+}
diff --git a/grub-core/disk/i386/pc/biosdisk.c b/grub-core/disk/i386/pc/biosdisk.c
new file mode 100644
index 0000000..1d47dc7
--- /dev/null
+++ b/grub-core/disk/i386/pc/biosdisk.c
@@ -0,0 +1,641 @@
+/*
+ * 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>
+
+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 (int (*hook) (const char *name), int drive)
+{
+ char name[10];
+
+ if (cd_drive && drive == cd_drive)
+ return hook ("cd");
+
+ grub_snprintf (name, sizeof (name),
+ (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80));
+ return hook (name);
+}
+
+static int
+grub_biosdisk_iterate (int (*hook) (const char *name))
+{
+ int drive;
+ int num_floppies;
+
+ /* For hard disks, attempt to read the MBR. */
+ 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, drive))
+ return 1;
+ }
+
+ if (cd_drive)
+ {
+ if (grub_biosdisk_call_hook (hook, 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, drive))
+ return 1;
+
+ 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 = 32;
+ /* TODO: get the correct size. */
+ total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ }
+ else
+ {
+ /* HDD */
+ int version;
+
+ 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 = drp->cylinders * drp->heads * drp->sectors;
+ }
+ }
+ }
+
+ 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 (! total_sectors)
+ total_sectors = data->cylinders * data->heads * data->sectors;
+ }
+
+ disk->total_sectors = total_sectors;
+ 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;
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_LBA)
+ {
+ struct grub_biosdisk_dap *dap;
+
+ dap = (struct grub_biosdisk_dap *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR
+ + (data->sectors
+ << GRUB_DISK_SECTOR_BITS));
+ 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, "can\'t write to cdrom");
+
+ dap->blocks = ALIGN_UP (dap->blocks, 4) >> 2;
+ dap->block >>= 2;
+
+ 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, "cdrom read error");
+ }
+ 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, "%s out of disk", 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, "%s out of disk", 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, "%s read error", disk->name);
+ case GRUB_BIOSDISK_WRITE:
+ return grub_error (GRUB_ERR_WRITE_ERROR, "%s write error", 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_addr_t sector, grub_uint32_t sectors)
+{
+ grub_size_t size;
+ grub_uint32_t offset;
+
+ /* OFFSET = SECTOR % SECTORS */
+ grub_divmod64 (sector, sectors, &offset);
+
+ size = sectors - offset;
+
+ /* Limit the max to 0x7f because of Phoenix EDD. */
+ if (size > 0x7f)
+ size = 0x7f;
+
+ return size;
+}
+
+static grub_err_t
+grub_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ struct grub_biosdisk_data *data = disk->data;
+
+ while (size)
+ {
+ grub_size_t len;
+ grub_size_t cdoff = 0;
+
+ len = get_safe_sectors (sector, data->sectors);
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ {
+ cdoff = (sector & 3) << GRUB_DISK_SECTOR_BITS;
+ len = ALIGN_UP (sector + len, 4) - (sector & ~3);
+ sector &= ~3;
+ }
+
+ 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 + cdoff),
+ len << GRUB_DISK_SECTOR_BITS);
+ buf += len << GRUB_DISK_SECTOR_BITS;
+ 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, "can't write to CDROM");
+
+ while (size)
+ {
+ grub_size_t len;
+
+ len = get_safe_sectors (sector, data->sectors);
+ if (len > size)
+ len = size;
+
+ grub_memcpy ((void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, buf,
+ len << GRUB_DISK_SECTOR_BITS);
+
+ if (grub_biosdisk_rw (GRUB_BIOSDISK_WRITE, disk, sector, len,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG))
+ return grub_errno;
+
+ buf += len << GRUB_DISK_SECTOR_BITS;
+ sector += len;
+ size -= len;
+ }
+
+ return grub_errno;
+}
+
+static struct grub_disk_dev grub_biosdisk_dev =
+ {
+ .name = "biosdisk",
+ .id = GRUB_DISK_DEVICE_BIOSDISK_ID,
+ .iterate = grub_biosdisk_iterate,
+ .open = grub_biosdisk_open,
+ .close = grub_biosdisk_close,
+ .read = grub_biosdisk_read,
+ .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;
+
+ if (grub_disk_firmware_is_tainted)
+ {
+ grub_printf ("Firmware is marked as tainted, refusing to initialize.\n");
+ return;
+ }
+ grub_disk_firmware_fini = grub_disk_biosdisk_fini;
+
+ grub_memset (cdrp, 0, sizeof (*cdrp));
+ cdrp->size = sizeof (*cdrp);
+ cdrp->media_type = 0xFF;
+ if ((! grub_biosdisk_get_cdinfo_int13_extensions (grub_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 (grub_boot_drive >= 0x90)
+ cd_drive = grub_boot_drive;
+
+ grub_disk_dev_register (&grub_biosdisk_dev);
+}
+
+GRUB_MOD_FINI(biosdisk)
+{
+ grub_disk_biosdisk_fini ();
+}
diff --git a/grub-core/disk/ieee1275/nand.c b/grub-core/disk/ieee1275/nand.c
new file mode 100644
index 0000000..e945016
--- /dev/null
+++ b/grub-core/disk/ieee1275/nand.c
@@ -0,0 +1,216 @@
+/* nand.c - NAND flash disk access. */
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/ieee1275/ieee1275.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_nand_data
+{
+ grub_ieee1275_ihandle_t handle;
+ grub_uint32_t block_size;
+};
+
+static int
+grub_nand_iterate (int (*hook) (const char *name))
+{
+ auto int dev_iterate (struct grub_ieee1275_devalias *alias);
+ int dev_iterate (struct grub_ieee1275_devalias *alias)
+ {
+ if (! grub_strcmp (alias->name, "nand"))
+ {
+ hook (alias->name);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ return grub_devalias_iterate (dev_iterate);
+}
+
+static grub_err_t
+grub_nand_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf);
+
+static grub_err_t
+grub_nand_open (const char *name, grub_disk_t disk)
+{
+ grub_ieee1275_ihandle_t dev_ihandle = 0;
+ struct grub_nand_data *data = 0;
+ struct size_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t ihandle;
+ grub_ieee1275_cell_t result;
+ grub_ieee1275_cell_t size1;
+ grub_ieee1275_cell_t size2;
+ } args;
+
+ if (! grub_strstr (name, "nand"))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a NAND device");
+
+ data = grub_malloc (sizeof (*data));
+ if (! data)
+ goto fail;
+
+ grub_ieee1275_open (name, &dev_ihandle);
+ if (! dev_ihandle)
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+ goto fail;
+ }
+
+ data->handle = dev_ihandle;
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 2);
+ args.method = (grub_ieee1275_cell_t) "block-size";
+ args.ihandle = dev_ihandle;
+ args.result = 1;
+
+ if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't get block size");
+ goto fail;
+ }
+
+ data->block_size = (args.size1 >> GRUB_DISK_SECTOR_BITS);
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 3);
+ args.method = (grub_ieee1275_cell_t) "size";
+ args.ihandle = dev_ihandle;
+ args.result = 1;
+
+ if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't get disk size");
+ goto fail;
+ }
+
+ disk->total_sectors = args.size1;
+ disk->total_sectors <<= 32;
+ disk->total_sectors += args.size2;
+ disk->total_sectors >>= GRUB_DISK_SECTOR_BITS;
+
+ disk->id = dev_ihandle;
+
+ disk->data = data;
+
+ return 0;
+
+fail:
+ if (dev_ihandle)
+ grub_ieee1275_close (dev_ihandle);
+ grub_free (data);
+ return grub_errno;
+}
+
+static void
+grub_nand_close (grub_disk_t disk)
+{
+ grub_ieee1275_close (((struct grub_nand_data *) disk->data)->handle);
+ grub_free (disk->data);
+}
+
+static grub_err_t
+grub_nand_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ struct grub_nand_data *data = disk->data;
+ grub_size_t bsize, ofs;
+
+ struct read_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t ihandle;
+ grub_ieee1275_cell_t ofs;
+ grub_ieee1275_cell_t page;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t buf;
+ grub_ieee1275_cell_t result;
+ } args;
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 6, 1);
+ args.method = (grub_ieee1275_cell_t) "pio-read";
+ args.ihandle = data->handle;
+ args.buf = (grub_ieee1275_cell_t) buf;
+ args.page = (grub_ieee1275_cell_t) ((grub_size_t) sector / data->block_size);
+
+ ofs = ((grub_size_t) sector % data->block_size) << GRUB_DISK_SECTOR_BITS;
+ size <<= GRUB_DISK_SECTOR_BITS;
+ bsize = (data->block_size << GRUB_DISK_SECTOR_BITS);
+
+ do
+ {
+ grub_size_t len;
+
+ len = (ofs + size > bsize) ? (bsize - ofs) : size;
+
+ args.len = (grub_ieee1275_cell_t) len;
+ args.ofs = (grub_ieee1275_cell_t) ofs;
+ args.result = 1;
+
+ if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result))
+ return grub_error (GRUB_ERR_READ_ERROR, "read error");
+
+ ofs = 0;
+ size -= len;
+ args.buf += len;
+ args.page++;
+ } while (size);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_nand_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+static struct grub_disk_dev grub_nand_dev =
+ {
+ .name = "nand",
+ .id = GRUB_DISK_DEVICE_NAND_ID,
+ .iterate = grub_nand_iterate,
+ .open = grub_nand_open,
+ .close = grub_nand_close,
+ .read = grub_nand_read,
+ .write = grub_nand_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(nand)
+{
+ grub_disk_dev_register (&grub_nand_dev);
+}
+
+GRUB_MOD_FINI(nand)
+{
+ grub_disk_dev_unregister (&grub_nand_dev);
+}
diff --git a/grub-core/disk/ieee1275/ofdisk.c b/grub-core/disk/ieee1275/ofdisk.c
new file mode 100644
index 0000000..0a935d5
--- /dev/null
+++ b/grub-core/disk/ieee1275/ofdisk.c
@@ -0,0 +1,373 @@
+/* ofdisk.c - Open Firmware disk access. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2004,2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/misc.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/ieee1275/ieee1275.h>
+#include <grub/ieee1275/ofdisk.h>
+
+static char *last_devpath;
+static grub_ieee1275_ihandle_t last_ihandle;
+
+struct ofdisk_hash_ent
+{
+ char *devpath;
+ /* Pointer to shortest available name on nodes representing canonical names,
+ otherwise NULL. */
+ const char *shortest;
+ struct ofdisk_hash_ent *next;
+};
+
+#define OFDISK_HASH_SZ 8
+static struct ofdisk_hash_ent *ofdisk_hash[OFDISK_HASH_SZ];
+
+static int
+ofdisk_hash_fn (const char *devpath)
+{
+ int hash = 0;
+ while (*devpath)
+ hash ^= *devpath++;
+ return (hash & (OFDISK_HASH_SZ - 1));
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_find (const char *devpath)
+{
+ struct ofdisk_hash_ent *p = ofdisk_hash[ofdisk_hash_fn(devpath)];
+
+ while (p)
+ {
+ if (!grub_strcmp (p->devpath, devpath))
+ break;
+ p = p->next;
+ }
+ return p;
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_add_real (char *devpath)
+{
+ struct ofdisk_hash_ent *p;
+ struct ofdisk_hash_ent **head = &ofdisk_hash[ofdisk_hash_fn(devpath)];
+
+ p = grub_malloc(sizeof (*p));
+ if (!p)
+ return NULL;
+
+ p->devpath = devpath;
+ p->next = *head;
+ p->shortest = 0;
+ *head = p;
+ return p;
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_add (char *devpath, char *curcan)
+{
+ struct ofdisk_hash_ent *p, *pcan;
+
+ p = ofdisk_hash_add_real (devpath);
+
+ grub_dprintf ("disk", "devpath = %s, canonical = %s\n", devpath, curcan);
+
+ if (!curcan)
+ {
+ p->shortest = devpath;
+ return p;
+ }
+
+ pcan = ofdisk_hash_find (curcan);
+ if (!pcan)
+ pcan = ofdisk_hash_add_real (curcan);
+ else
+ grub_free (curcan);
+
+ if (!pcan)
+ grub_errno = GRUB_ERR_NONE;
+ else
+ {
+ if (!pcan->shortest
+ || grub_strlen (pcan->shortest) > grub_strlen (devpath))
+ pcan->shortest = devpath;
+ }
+
+ return p;
+}
+
+static void
+scan (void)
+{
+ auto int dev_iterate (struct grub_ieee1275_devalias *alias);
+
+ int dev_iterate (struct grub_ieee1275_devalias *alias)
+ {
+ struct ofdisk_hash_ent *op;
+
+ grub_dprintf ("disk", "device name = %s type = %s\n", alias->name,
+ alias->type);
+
+ if (grub_strcmp (alias->type, "block") != 0)
+ return 0;
+
+ grub_dprintf ("disk", "disk name = %s\n", alias->name);
+ grub_dprintf ("disk", "disk name = %s, path = %s\n", alias->name,
+ alias->path);
+
+ op = ofdisk_hash_find (alias->name);
+ if (!op)
+ {
+ char *name = grub_strdup (alias->name);
+ char *can = grub_strdup (alias->path);
+ if (!name || !can)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ grub_free (name);
+ grub_free (can);
+ return 0;
+ }
+ op = ofdisk_hash_add (name, can);
+ }
+ return 0;
+ }
+
+ grub_devalias_iterate (dev_iterate);
+ grub_ieee1275_devices_iterate (dev_iterate);
+}
+
+static int
+grub_ofdisk_iterate (int (*hook) (const char *name))
+{
+ unsigned i;
+ scan ();
+
+ for (i = 0; i < ARRAY_SIZE (ofdisk_hash); i++)
+ {
+ static struct ofdisk_hash_ent *ent;
+ for (ent = ofdisk_hash[i]; ent; ent = ent->next)
+ {
+ if (!ent->shortest)
+ continue;
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_OFDISK_SDCARD_ONLY))
+ {
+ grub_ieee1275_phandle_t dev;
+ char tmp[8];
+
+ if (grub_ieee1275_finddevice (ent->devpath, &dev))
+ {
+ grub_dprintf ("disk", "finddevice (%s) failed\n",
+ ent->devpath);
+ continue;
+ }
+
+ if (grub_ieee1275_get_property (dev, "iconname", tmp,
+ sizeof tmp, 0))
+ {
+ grub_dprintf ("disk", "get iconname failed\n");
+ continue;
+ }
+
+ if (grub_strcmp (tmp, "sdmmc") != 0)
+ {
+ grub_dprintf ("disk", "device is not an SD card\n");
+ continue;
+ }
+ }
+
+ if (grub_strncmp (ent->shortest, "cdrom", 5) == 0)
+ continue;
+
+ if (hook (ent->shortest))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static char *
+compute_dev_path (const char *name)
+{
+ char *devpath = grub_malloc (grub_strlen (name) + 3);
+ char *p, c;
+
+ if (!devpath)
+ return NULL;
+
+ /* Un-escape commas. */
+ p = devpath;
+ while ((c = *name++) != '\0')
+ {
+ if (c == '\\' && *name == ',')
+ {
+ *p++ = ',';
+ name++;
+ }
+ else
+ *p++ = c;
+ }
+
+ *p++ = '\0';
+
+ return devpath;
+}
+
+static grub_err_t
+grub_ofdisk_open (const char *name, grub_disk_t disk)
+{
+ grub_ieee1275_phandle_t dev;
+ char *devpath;
+ /* XXX: This should be large enough for any possible case. */
+ char prop[64];
+ grub_ssize_t actual;
+
+ devpath = compute_dev_path (name);
+ if (! devpath)
+ return grub_errno;
+
+ grub_dprintf ("disk", "Opening `%s'.\n", devpath);
+
+ if (grub_ieee1275_finddevice (devpath, &dev))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read device properties");
+
+ if (grub_ieee1275_get_property (dev, "device_type", prop, sizeof (prop),
+ &actual))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read the device type");
+
+ if (grub_strcmp (prop, "block"))
+ return grub_error (GRUB_ERR_BAD_DEVICE, "not a block device");
+
+ /* XXX: There is no property to read the number of blocks. There
+ should be a property `#blocks', but it is not there. Perhaps it
+ is possible to use seek for this. */
+ disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+
+ {
+ struct ofdisk_hash_ent *op;
+ op = ofdisk_hash_find (devpath);
+ if (!op)
+ op = ofdisk_hash_add (devpath, NULL);
+ else
+ grub_free (devpath);
+ if (!op)
+ return grub_errno;
+ disk->id = (unsigned long) op;
+ disk->data = op->devpath;
+ }
+
+ return 0;
+}
+
+static void
+grub_ofdisk_close (grub_disk_t disk)
+{
+ if (disk->data == last_devpath)
+ {
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+ }
+ disk->data = 0;
+}
+
+static grub_err_t
+grub_ofdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_ssize_t status, actual;
+ unsigned long long pos;
+
+ if (disk->data != last_devpath)
+ {
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+
+ if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PARTITION_0))
+ {
+ char name2[grub_strlen (disk->data) + 3];
+ char *p;
+
+ grub_strcpy (name2, disk->data);
+ p = name2 + grub_strlen (name2);
+ *p++ = ':';
+ *p++ = '0';
+ *p = 0;
+ grub_ieee1275_open (name2, &last_ihandle);
+ }
+ else
+ grub_ieee1275_open (disk->data, &last_ihandle);
+ if (! last_ihandle)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+ last_devpath = disk->data;
+ }
+
+ pos = sector * 512UL;
+
+ grub_ieee1275_seek (last_ihandle, pos, &status);
+ if (status < 0)
+ return grub_error (GRUB_ERR_READ_ERROR,
+ "seek error, can't seek block %llu",
+ (long long) sector);
+ grub_ieee1275_read (last_ihandle, buf, size * 512UL, &actual);
+ if (actual != (grub_ssize_t) (size * 512UL))
+ return grub_error (GRUB_ERR_READ_ERROR, "read error on block: %llu",
+ (long long) sector);
+
+ return 0;
+}
+
+static grub_err_t
+grub_ofdisk_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+static struct grub_disk_dev grub_ofdisk_dev =
+ {
+ .name = "ofdisk",
+ .id = GRUB_DISK_DEVICE_OFDISK_ID,
+ .iterate = grub_ofdisk_iterate,
+ .open = grub_ofdisk_open,
+ .close = grub_ofdisk_close,
+ .read = grub_ofdisk_read,
+ .write = grub_ofdisk_write,
+ .next = 0
+ };
+
+void
+grub_ofdisk_init (void)
+{
+ grub_disk_dev_register (&grub_ofdisk_dev);
+}
+
+void
+grub_ofdisk_fini (void)
+{
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+
+ grub_disk_dev_unregister (&grub_ofdisk_dev);
+}
diff --git a/grub-core/disk/loopback.c b/grub-core/disk/loopback.c
new file mode 100644
index 0000000..d50f353
--- /dev/null
+++ b/grub-core/disk/loopback.c
@@ -0,0 +1,232 @@
+/* loopback.c - command to add loopback devices. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2005,2006,2007 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/dl.h>
+#include <grub/misc.h>
+#include <grub/file.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_loopback
+{
+ char *devname;
+ grub_file_t file;
+ struct grub_loopback *next;
+};
+
+static struct grub_loopback *loopback_list;
+
+static const struct grub_arg_option options[] =
+ {
+ {"delete", 'd', 0, N_("Delete the loopback device entry."), 0, 0},
+ {0, 0, 0, 0, 0, 0}
+ };
+
+/* Delete the loopback device NAME. */
+static grub_err_t
+delete_loopback (const char *name)
+{
+ struct grub_loopback *dev;
+ struct grub_loopback **prev;
+
+ /* Search for the device. */
+ for (dev = loopback_list, prev = &loopback_list;
+ dev;
+ prev = &dev->next, dev = dev->next)
+ if (grub_strcmp (dev->devname, name) == 0)
+ break;
+
+ if (! dev)
+ return grub_error (GRUB_ERR_BAD_DEVICE, "device not found");
+
+ /* Remove the device from the list. */
+ *prev = dev->next;
+
+ grub_free (dev->devname);
+ grub_file_close (dev->file);
+ grub_free (dev);
+
+ return 0;
+}
+
+/* The command to add and remove loopback devices. */
+static grub_err_t
+grub_cmd_loopback (grub_extcmd_context_t ctxt, int argc, char **args)
+{
+ struct grub_arg_list *state = ctxt->state;
+ grub_file_t file;
+ struct grub_loopback *newdev;
+ grub_err_t ret;
+
+ if (argc < 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required");
+
+ /* Check if `-d' was used. */
+ if (state[0].set)
+ return delete_loopback (args[0]);
+
+ if (argc < 2)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "file name required");
+
+ file = grub_file_open (args[1]);
+ if (! file)
+ return grub_errno;
+
+ /* First try to replace the old device. */
+ for (newdev = loopback_list; newdev; newdev = newdev->next)
+ if (grub_strcmp (newdev->devname, args[0]) == 0)
+ break;
+
+ if (newdev)
+ {
+ grub_file_close (newdev->file);
+ newdev->file = file;
+
+ return 0;
+ }
+
+ /* Unable to replace it, make a new entry. */
+ newdev = grub_malloc (sizeof (struct grub_loopback));
+ if (! newdev)
+ goto fail;
+
+ newdev->devname = grub_strdup (args[0]);
+ if (! newdev->devname)
+ {
+ grub_free (newdev);
+ goto fail;
+ }
+
+ newdev->file = file;
+
+ /* Add the new entry to the list. */
+ newdev->next = loopback_list;
+ loopback_list = newdev;
+
+ return 0;
+
+fail:
+ ret = grub_errno;
+ grub_file_close (file);
+ return ret;
+}
+
+
+static int
+grub_loopback_iterate (int (*hook) (const char *name))
+{
+ struct grub_loopback *d;
+ for (d = loopback_list; d; d = d->next)
+ {
+ if (hook (d->devname))
+ return 1;
+ }
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_open (const char *name, grub_disk_t disk)
+{
+ struct grub_loopback *dev;
+
+ for (dev = loopback_list; dev; dev = dev->next)
+ if (grub_strcmp (dev->devname, name) == 0)
+ break;
+
+ if (! dev)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+
+ /* Use the filesize for the disk size, round up to a complete sector. */
+ if (dev->file->size != GRUB_FILE_SIZE_UNKNOWN)
+ disk->total_sectors = ((dev->file->size + GRUB_DISK_SECTOR_SIZE - 1)
+ / GRUB_DISK_SECTOR_SIZE);
+ else
+ disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ disk->id = (unsigned long) dev;
+
+ disk->data = dev;
+
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_file_t file = ((struct grub_loopback *) disk->data)->file;
+ grub_off_t pos;
+
+ grub_file_seek (file, sector << GRUB_DISK_SECTOR_BITS);
+
+ grub_file_read (file, buf, size << GRUB_DISK_SECTOR_BITS);
+ if (grub_errno)
+ return grub_errno;
+
+ /* In case there is more data read than there is available, in case
+ of files that are not a multiple of GRUB_DISK_SECTOR_SIZE, fill
+ the rest with zeros. */
+ pos = (sector + size) << GRUB_DISK_SECTOR_BITS;
+ if (pos > file->size)
+ {
+ grub_size_t amount = pos - file->size;
+ grub_memset (buf + (size << GRUB_DISK_SECTOR_BITS) - amount, 0, amount);
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+static struct grub_disk_dev grub_loopback_dev =
+ {
+ .name = "loopback",
+ .id = GRUB_DISK_DEVICE_LOOPBACK_ID,
+ .iterate = grub_loopback_iterate,
+ .open = grub_loopback_open,
+ .read = grub_loopback_read,
+ .write = grub_loopback_write,
+ .next = 0
+ };
+
+static grub_extcmd_t cmd;
+
+GRUB_MOD_INIT(loopback)
+{
+ cmd = grub_register_extcmd ("loopback", grub_cmd_loopback, 0,
+ N_("[-d] DEVICENAME FILE."),
+ N_("Make a device of a file."), options);
+ grub_disk_dev_register (&grub_loopback_dev);
+}
+
+GRUB_MOD_FINI(loopback)
+{
+ grub_unregister_extcmd (cmd);
+ grub_disk_dev_unregister (&grub_loopback_dev);
+}
diff --git a/grub-core/disk/lvm.c b/grub-core/disk/lvm.c
new file mode 100644
index 0000000..206e3e2
--- /dev/null
+++ b/grub-core/disk/lvm.c
@@ -0,0 +1,896 @@
+/* lvm.c - module to read Logical Volumes. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/lvm.h>
+
+#ifdef GRUB_UTIL
+#include <grub/emu/misc.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static struct grub_lvm_vg *vg_list;
+static int lv_count;
+
+
+/* Go the string STR and return the number after STR. *P will point
+ at the number. In case STR is not found, *P will be NULL and the
+ return value will be 0. */
+static int
+grub_lvm_getvalue (char **p, char *str)
+{
+ *p = grub_strstr (*p, str);
+ if (! *p)
+ return 0;
+ *p += grub_strlen (str);
+ return grub_strtoul (*p, NULL, 10);
+}
+
+#if 0
+static int
+grub_lvm_checkvalue (char **p, char *str, char *tmpl)
+{
+ int tmpllen = grub_strlen (tmpl);
+ *p = grub_strstr (*p, str);
+ if (! *p)
+ return 0;
+ *p += grub_strlen (str);
+ if (**p != '"')
+ return 0;
+ return (grub_memcmp (*p + 1, tmpl, tmpllen) == 0 && (*p)[tmpllen + 1] == '"');
+}
+#endif
+
+static int
+grub_lvm_check_flag (char *p, char *str, char *flag)
+{
+ int len_str = grub_strlen (str), len_flag = grub_strlen (flag);
+ while (1)
+ {
+ char *q;
+ p = grub_strstr (p, str);
+ if (! p)
+ return 0;
+ p += len_str;
+ if (grub_memcmp (p, " = [", sizeof (" = [") - 1) != 0)
+ continue;
+ q = p + sizeof (" = [") - 1;
+ while (1)
+ {
+ while (grub_isspace (*q))
+ q++;
+ if (*q != '"')
+ return 0;
+ q++;
+ if (grub_memcmp (q, flag, len_flag) == 0 && q[len_flag] == '"')
+ return 1;
+ while (*q != '"')
+ q++;
+ q++;
+ if (*q == ']')
+ return 0;
+ q++;
+ }
+ }
+}
+
+static int
+grub_lvm_iterate (int (*hook) (const char *name))
+{
+ struct grub_lvm_vg *vg;
+ for (vg = vg_list; vg; vg = vg->next)
+ {
+ struct grub_lvm_lv *lv;
+ if (vg->lvs)
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (lv->visible && hook (lv->name))
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef GRUB_UTIL
+static grub_disk_memberlist_t
+grub_lvm_memberlist (grub_disk_t disk)
+{
+ struct grub_lvm_lv *lv = disk->data;
+ grub_disk_memberlist_t list = NULL, tmp;
+ struct grub_lvm_pv *pv;
+
+ if (lv->vg->pvs)
+ for (pv = lv->vg->pvs; pv; pv = pv->next)
+ {
+ if (!pv->disk)
+ grub_util_error ("Couldn't find PV %s. Check your device.map",
+ pv->name);
+ tmp = grub_malloc (sizeof (*tmp));
+ tmp->disk = pv->disk;
+ tmp->next = list;
+ list = tmp;
+ }
+
+ return list;
+}
+#endif
+
+static grub_err_t
+grub_lvm_open (const char *name, grub_disk_t disk)
+{
+ struct grub_lvm_vg *vg;
+ struct grub_lvm_lv *lv = NULL;
+ for (vg = vg_list; vg; vg = vg->next)
+ {
+ if (vg->lvs)
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (! grub_strcmp (lv->name, name))
+ break;
+
+ if (lv)
+ break;
+ }
+
+ if (! lv)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown LVM device %s", name);
+
+ disk->id = lv->number;
+ disk->data = lv;
+ disk->total_sectors = lv->size;
+
+ return 0;
+}
+
+static void
+grub_lvm_close (grub_disk_t disk __attribute ((unused)))
+{
+ return;
+}
+
+static grub_err_t
+read_lv (struct grub_lvm_lv *lv, grub_disk_addr_t sector,
+ grub_size_t size, char *buf);
+
+static grub_err_t
+read_node (const struct grub_lvm_node *node, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ /* Check whether we actually know the physical volume we want to
+ read from. */
+ if (node->pv)
+ {
+ if (node->pv->disk)
+ return grub_disk_read (node->pv->disk, sector + node->pv->start, 0,
+ size << GRUB_DISK_SECTOR_BITS, buf);
+ else
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "physical volume %s not found", node->pv->name);
+
+ }
+ if (node->lv)
+ return read_lv (node->lv, sector, size, buf);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown node '%s'", node->name);
+}
+
+static grub_err_t
+read_lv (struct grub_lvm_lv *lv, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_err_t err = 0;
+ struct grub_lvm_vg *vg = lv->vg;
+ struct grub_lvm_segment *seg = lv->segments;
+ struct grub_lvm_node *node;
+ grub_uint64_t offset;
+ grub_uint64_t extent;
+ unsigned int i;
+
+ if (!lv)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown volume");
+
+ extent = grub_divmod64 (sector, vg->extent_size, NULL);
+
+ /* Find the right segment. */
+ for (i = 0; i < lv->segment_count; i++)
+ {
+ if ((seg->start_extent <= extent)
+ && ((seg->start_extent + seg->extent_count) > extent))
+ {
+ break;
+ }
+
+ seg++;
+ }
+
+ if (i == lv->segment_count)
+ return grub_error (GRUB_ERR_READ_ERROR, "incorrect segment");
+
+ switch (seg->type)
+ {
+ case GRUB_LVM_STRIPED:
+ if (seg->node_count == 1)
+ {
+ /* This segment is linear, so that's easy. We just need to find
+ out the offset in the physical volume and read SIZE bytes
+ from that. */
+ struct grub_lvm_node *stripe = seg->nodes;
+ grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
+
+ node = stripe;
+ seg_offset = ((grub_uint64_t) stripe->start
+ * (grub_uint64_t) vg->extent_size);
+
+ offset = sector - ((grub_uint64_t) seg->start_extent
+ * (grub_uint64_t) vg->extent_size) + seg_offset;
+ }
+ else
+ {
+ /* This is a striped segment. We have to find the right PV
+ similar to RAID0. */
+ struct grub_lvm_node *stripe = seg->nodes;
+ grub_uint32_t a, b;
+ grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
+ unsigned int stripenr;
+
+ offset = sector - ((grub_uint64_t) seg->start_extent
+ * (grub_uint64_t) vg->extent_size);
+
+ a = grub_divmod64 (offset, seg->stripe_size, NULL);
+ grub_divmod64 (a, seg->node_count, &stripenr);
+
+ a = grub_divmod64 (offset, seg->stripe_size * seg->node_count, NULL);
+ grub_divmod64 (offset, seg->stripe_size, &b);
+ offset = a * seg->stripe_size + b;
+
+ stripe += stripenr;
+ node = stripe;
+
+ seg_offset = ((grub_uint64_t) stripe->start
+ * (grub_uint64_t) vg->extent_size);
+
+ offset += seg_offset;
+ }
+ return read_node (node, offset, size, buf);
+ case GRUB_LVM_MIRROR:
+ i = 0;
+ while (1)
+ {
+ err = read_node (&seg->nodes[i], sector, size, buf);
+ if (!err)
+ return err;
+ if (++i >= seg->node_count)
+ return err;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+ return grub_error (GRUB_ERR_IO, "unknown LVM segment");
+}
+
+static grub_err_t
+grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ return read_lv (disk->data, sector, size, buf);
+}
+
+static grub_err_t
+grub_lvm_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+static int
+grub_lvm_scan_device (const char *name)
+{
+ grub_err_t err;
+ grub_disk_t disk;
+ grub_uint64_t mda_offset, mda_size;
+ char buf[GRUB_LVM_LABEL_SIZE];
+ char vg_id[GRUB_LVM_ID_STRLEN+1];
+ char pv_id[GRUB_LVM_ID_STRLEN+1];
+ char *metadatabuf, *p, *q, *vgname;
+ struct grub_lvm_label_header *lh = (struct grub_lvm_label_header *) buf;
+ struct grub_lvm_pv_header *pvh;
+ struct grub_lvm_disk_locn *dlocn;
+ struct grub_lvm_mda_header *mdah;
+ struct grub_lvm_raw_locn *rlocn;
+ unsigned int i, j, vgname_len;
+ struct grub_lvm_vg *vg;
+ struct grub_lvm_pv *pv;
+
+#ifdef GRUB_UTIL
+ grub_util_info ("scanning %s for LVM", name);
+#endif
+
+ disk = grub_disk_open (name);
+ if (!disk)
+ {
+ if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+
+ /* Search for label. */
+ for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
+ {
+ err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
+ if (err)
+ goto fail;
+
+ if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
+ sizeof (lh->id)))
+ && (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
+ sizeof (lh->type))))
+ break;
+ }
+
+ /* Return if we didn't find a label. */
+ if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("no LVM signature found");
+#endif
+ goto fail;
+ }
+
+ pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
+
+ for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
+ {
+ pv_id[j++] = pvh->pv_uuid[i];
+ if ((i != 1) && (i != 29) && (i % 4 == 1))
+ pv_id[j++] = '-';
+ }
+ pv_id[j] = '\0';
+
+ dlocn = pvh->disk_areas_xl;
+
+ dlocn++;
+ /* Is it possible to have multiple data/metadata areas? I haven't
+ seen devices that have it. */
+ if (dlocn->offset)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "we don't support multiple LVM data areas");
+
+#ifdef GRUB_UTIL
+ grub_util_info ("we don't support multiple LVM data areas\n");
+#endif
+ goto fail;
+ }
+
+ dlocn++;
+ mda_offset = grub_le_to_cpu64 (dlocn->offset);
+ mda_size = grub_le_to_cpu64 (dlocn->size);
+
+ /* It's possible to have multiple copies of metadata areas, we just use the
+ first one. */
+
+ /* Allocate buffer space for the circular worst-case scenario. */
+ metadatabuf = grub_malloc (2 * mda_size);
+ if (! metadatabuf)
+ goto fail;
+
+ err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
+ if (err)
+ goto fail2;
+
+ mdah = (struct grub_lvm_mda_header *) metadatabuf;
+ if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
+ sizeof (mdah->magic)))
+ || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unknown LVM metadata header");
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown LVM metadata header\n");
+#endif
+ goto fail2;
+ }
+
+ rlocn = mdah->raw_locns;
+ if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
+ grub_le_to_cpu64 (mdah->size))
+ {
+ /* Metadata is circular. Copy the wrap in place. */
+ grub_memcpy (metadatabuf + mda_size,
+ metadatabuf + GRUB_LVM_MDA_HEADER_SIZE,
+ grub_le_to_cpu64 (rlocn->offset) +
+ grub_le_to_cpu64 (rlocn->size) -
+ grub_le_to_cpu64 (mdah->size));
+ }
+ p = q = metadatabuf + grub_le_to_cpu64 (rlocn->offset);
+
+ while (*q != ' ' && q < metadatabuf + mda_size)
+ q++;
+
+ if (q == metadatabuf + mda_size)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("error parsing metadata\n");
+#endif
+ goto fail2;
+ }
+
+ vgname_len = q - p;
+ vgname = grub_malloc (vgname_len + 1);
+ if (!vgname)
+ goto fail2;
+
+ grub_memcpy (vgname, p, vgname_len);
+ vgname[vgname_len] = '\0';
+
+ p = grub_strstr (q, "id = \"");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("couldn't find ID\n");
+#endif
+ goto fail3;
+ }
+ p += sizeof ("id = \"") - 1;
+ grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
+ vg_id[GRUB_LVM_ID_STRLEN] = '\0';
+
+ for (vg = vg_list; vg; vg = vg->next)
+ {
+ if (! grub_memcmp(vg_id, vg->id, GRUB_LVM_ID_STRLEN))
+ break;
+ }
+
+ if (! vg)
+ {
+ /* First time we see this volume group. We've to create the
+ whole volume group structure. */
+ vg = grub_malloc (sizeof (*vg));
+ if (! vg)
+ goto fail3;
+ vg->name = vgname;
+ grub_memcpy (vg->id, vg_id, GRUB_LVM_ID_STRLEN+1);
+
+ vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown extent size\n");
+#endif
+ goto fail4;
+ }
+
+ vg->lvs = NULL;
+ vg->pvs = NULL;
+
+ p = grub_strstr (p, "physical_volumes {");
+ if (p)
+ {
+ p += sizeof ("physical_volumes {") - 1;
+
+ /* Add all the pvs to the volume group. */
+ while (1)
+ {
+ int s;
+ while (grub_isspace (*p))
+ p++;
+
+ if (*p == '}')
+ break;
+
+ pv = grub_malloc (sizeof (*pv));
+ q = p;
+ while (*q != ' ')
+ q++;
+
+ s = q - p;
+ pv->name = grub_malloc (s + 1);
+ grub_memcpy (pv->name, p, s);
+ pv->name[s] = '\0';
+
+ p = grub_strstr (p, "id = \"");
+ if (p == NULL)
+ goto pvs_fail;
+ p += sizeof("id = \"") - 1;
+
+ grub_memcpy (pv->id, p, GRUB_LVM_ID_STRLEN);
+ pv->id[GRUB_LVM_ID_STRLEN] = '\0';
+
+ pv->start = grub_lvm_getvalue (&p, "pe_start = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown pe_start\n");
+#endif
+ goto pvs_fail;
+ }
+
+ p = grub_strchr (p, '}');
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("error parsing pe_start\n");
+#endif
+ goto pvs_fail;
+ }
+ p++;
+
+ pv->disk = NULL;
+ pv->next = vg->pvs;
+ vg->pvs = pv;
+
+ continue;
+ pvs_fail:
+ grub_free (pv->name);
+ grub_free (pv);
+ goto fail4;
+ }
+ }
+
+ p = grub_strstr (p, "logical_volumes");
+ if (p)
+ {
+ p += 18;
+
+ /* And add all the lvs to the volume group. */
+ while (1)
+ {
+ int s;
+ int skip_lv = 0;
+ struct grub_lvm_lv *lv;
+ struct grub_lvm_segment *seg;
+ int is_pvmove;
+
+ while (grub_isspace (*p))
+ p++;
+
+ if (*p == '}')
+ break;
+
+ lv = grub_malloc (sizeof (*lv));
+
+ q = p;
+ while (*q != ' ')
+ q++;
+
+ s = q - p;
+ lv->name = grub_malloc (vgname_len + 1 + s + 1);
+ grub_memcpy (lv->name, vgname, vgname_len);
+ lv->name[vgname_len] = '-';
+ grub_memcpy (lv->name + vgname_len + 1, p, s);
+ lv->name[vgname_len + 1 + s] = '\0';
+
+ lv->size = 0;
+
+ lv->visible = grub_lvm_check_flag (p, "status", "VISIBLE");
+ is_pvmove = grub_lvm_check_flag (p, "status", "PVMOVE");
+
+ lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown segment_count\n");
+#endif
+ goto lvs_fail;
+ }
+ lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
+ seg = lv->segments;
+
+ for (i = 0; i < lv->segment_count; i++)
+ {
+
+ p = grub_strstr (p, "segment");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown segment\n");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown start_extent\n");
+#endif
+ goto lvs_segment_fail;
+ }
+ seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown extent_count\n");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ p = grub_strstr (p, "type = \"");
+ if (p == NULL)
+ goto lvs_segment_fail;
+ p += sizeof("type = \"") - 1;
+
+ lv->size += seg->extent_count * vg->extent_size;
+
+ if (grub_memcmp (p, "striped\"",
+ sizeof ("striped\"") - 1) == 0)
+ {
+ struct grub_lvm_node *stripe;
+
+ seg->type = GRUB_LVM_STRIPED;
+ seg->node_count = grub_lvm_getvalue (&p, "stripe_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripe_count\n");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ if (seg->node_count != 1)
+ seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
+
+ seg->nodes = grub_zalloc (sizeof (*stripe)
+ * seg->node_count);
+ stripe = seg->nodes;
+
+ p = grub_strstr (p, "stripes = [");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripes\n");
+#endif
+ goto lvs_segment_fail2;
+ }
+ p += sizeof("stripes = [") - 1;
+
+ for (j = 0; j < seg->node_count; j++)
+ {
+ p = grub_strchr (p, '"');
+ if (p == NULL)
+ continue;
+ q = ++p;
+ while (*q != '"')
+ q++;
+
+ s = q - p;
+
+ stripe->name = grub_malloc (s + 1);
+ if (stripe->name == NULL)
+ goto lvs_segment_fail2;
+
+ grub_memcpy (stripe->name, p, s);
+ stripe->name[s] = '\0';
+
+ stripe->start = grub_lvm_getvalue (&p, ",");
+ if (p == NULL)
+ continue;
+
+ stripe++;
+ }
+ }
+ else if (grub_memcmp (p, "mirror\"", sizeof ("mirror\"") - 1)
+ == 0)
+ {
+ seg->type = GRUB_LVM_MIRROR;
+ seg->node_count = grub_lvm_getvalue (&p, "mirror_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown mirror_count\n");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ seg->nodes = grub_zalloc (sizeof (seg->nodes[0])
+ * seg->node_count);
+
+ p = grub_strstr (p, "mirrors = [");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown mirrors\n");
+#endif
+ goto lvs_segment_fail2;
+ }
+ p += sizeof("mirrors = [") - 1;
+
+ for (j = 0; j < seg->node_count; j++)
+ {
+ char *lvname;
+
+ p = grub_strchr (p, '"');
+ if (p == NULL)
+ continue;
+ q = ++p;
+ while (*q != '"')
+ q++;
+
+ s = q - p;
+
+ lvname = grub_malloc (s + 1);
+ if (lvname == NULL)
+ goto lvs_segment_fail2;
+
+ grub_memcpy (lvname, p, s);
+ lvname[s] = '\0';
+ seg->nodes[j].name = lvname;
+ p = q + 1;
+ }
+ /* Only first (original) is ok with in progress pvmove. */
+ if (is_pvmove)
+ seg->node_count = 1;
+ }
+ else
+ {
+#ifdef GRUB_UTIL
+ char *p2;
+ p2 = grub_strchr (p, '"');
+ if (p2)
+ *p2 = 0;
+ grub_util_info ("unknown LVM type %s\n", p);
+ if (p2)
+ *p2 ='"';
+#endif
+ /* Found a non-supported type, give up and move on. */
+ skip_lv = 1;
+ break;
+ }
+
+ seg++;
+
+ continue;
+ lvs_segment_fail2:
+ grub_free (seg->nodes);
+ lvs_segment_fail:
+ goto fail4;
+ }
+
+ if (p != NULL)
+ p = grub_strchr (p, '}');
+ if (p == NULL)
+ goto lvs_fail;
+ p += 3;
+
+ if (skip_lv)
+ {
+ grub_free (lv->name);
+ grub_free (lv);
+ continue;
+ }
+
+ lv->number = lv_count++;
+ lv->vg = vg;
+ lv->next = vg->lvs;
+ vg->lvs = lv;
+
+ continue;
+ lvs_fail:
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail4;
+ }
+ }
+
+ /* Match lvs. */
+ {
+ struct grub_lvm_lv *lv1;
+ struct grub_lvm_lv *lv2;
+ for (lv1 = vg->lvs; lv1; lv1 = lv1->next)
+ for (i = 0; i < lv1->segment_count; i++)
+ for (j = 0; j < lv1->segments[i].node_count; j++)
+ {
+ if (vg->pvs)
+ for (pv = vg->pvs; pv; pv = pv->next)
+ {
+ if (! grub_strcmp (pv->name,
+ lv1->segments[i].nodes[j].name))
+ {
+ lv1->segments[i].nodes[j].pv = pv;
+ break;
+ }
+ }
+ if (lv1->segments[i].nodes[j].pv == NULL)
+ for (lv2 = vg->lvs; lv2; lv2 = lv2->next)
+ if (grub_strcmp (lv2->name + grub_strlen (vg->name) + 1,
+ lv1->segments[i].nodes[j].name) == 0)
+ lv1->segments[i].nodes[j].lv = lv2;
+ }
+
+ }
+
+ vg->next = vg_list;
+ vg_list = vg;
+ }
+ else
+ {
+ grub_free (vgname);
+ }
+
+ /* Match the device we are currently reading from with the right
+ PV. */
+ if (vg->pvs)
+ for (pv = vg->pvs; pv; pv = pv->next)
+ {
+ if (! grub_memcmp (pv->id, pv_id, GRUB_LVM_ID_STRLEN))
+ {
+ /* This could happen to LVM on RAID, pv->disk points to the
+ raid device, we shouldn't change it. */
+ if (! pv->disk)
+ pv->disk = grub_disk_open (name);
+ break;
+ }
+ }
+
+ goto fail2;
+
+ /* Failure path. */
+ fail4:
+ grub_free (vg);
+ fail3:
+ grub_free (vgname);
+
+ /* Normal exit path. */
+ fail2:
+ grub_free (metadatabuf);
+ fail:
+ grub_disk_close (disk);
+ if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
+ grub_errno = GRUB_ERR_NONE;
+ grub_print_error ();
+ return 0;
+}
+
+static struct grub_disk_dev grub_lvm_dev =
+ {
+ .name = "lvm",
+ .id = GRUB_DISK_DEVICE_LVM_ID,
+ .iterate = grub_lvm_iterate,
+ .open = grub_lvm_open,
+ .close = grub_lvm_close,
+ .read = grub_lvm_read,
+ .write = grub_lvm_write,
+#ifdef GRUB_UTIL
+ .memberlist = grub_lvm_memberlist,
+#endif
+ .next = 0
+ };
+
+
+GRUB_MOD_INIT(lvm)
+{
+ grub_device_iterate (&grub_lvm_scan_device);
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ grub_disk_dev_register (&grub_lvm_dev);
+}
+
+GRUB_MOD_FINI(lvm)
+{
+ grub_disk_dev_unregister (&grub_lvm_dev);
+ vg_list = NULL;
+ /* FIXME: free the lvm list. */
+}
diff --git a/grub-core/disk/mdraid1x_linux.c b/grub-core/disk/mdraid1x_linux.c
new file mode 100644
index 0000000..19c43f4
--- /dev/null
+++ b/grub-core/disk/mdraid1x_linux.c
@@ -0,0 +1,247 @@
+/* mdraid_linux.c - module to handle Linux Software RAID. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Linux RAID on disk structures and constants,
+ copied from include/linux/raid/md_p.h. */
+
+#define SB_MAGIC 0xa92b4efc
+
+/*
+ * The version-1 superblock :
+ * All numeric fields are little-endian.
+ *
+ * Total size: 256 bytes plus 2 per device.
+ * 1K allows 384 devices.
+ */
+
+struct grub_raid_super_1x
+{
+ /* Constant array information - 128 bytes. */
+ grub_uint32_t magic; /* MD_SB_MAGIC: 0xa92b4efc - little endian. */
+ grub_uint32_t major_version; /* 1. */
+ grub_uint32_t feature_map; /* Bit 0 set if 'bitmap_offset' is meaningful. */
+ grub_uint32_t pad0; /* Always set to 0 when writing. */
+
+ grub_uint8_t set_uuid[16]; /* User-space generated. */
+ char set_name[32]; /* Set and interpreted by user-space. */
+
+ grub_uint64_t ctime; /* Lo 40 bits are seconds, top 24 are microseconds or 0. */
+ grub_uint32_t level; /* -4 (multipath), -1 (linear), 0,1,4,5. */
+ grub_uint32_t layout; /* only for raid5 and raid10 currently. */
+ grub_uint64_t size; /* Used size of component devices, in 512byte sectors. */
+
+ grub_uint32_t chunksize; /* In 512byte sectors. */
+ grub_uint32_t raid_disks;
+ grub_uint32_t bitmap_offset; /* Sectors after start of superblock that bitmap starts
+ * NOTE: signed, so bitmap can be before superblock
+ * only meaningful of feature_map[0] is set.
+ */
+
+ /* These are only valid with feature bit '4'. */
+ grub_uint32_t new_level; /* New level we are reshaping to. */
+ grub_uint64_t reshape_position; /* Next address in array-space for reshape. */
+ grub_uint32_t delta_disks; /* Change in number of raid_disks. */
+ grub_uint32_t new_layout; /* New layout. */
+ grub_uint32_t new_chunk; /* New chunk size (512byte sectors). */
+ grub_uint8_t pad1[128 - 124]; /* Set to 0 when written. */
+
+ /* Constant this-device information - 64 bytes. */
+ grub_uint64_t data_offset; /* Sector start of data, often 0. */
+ grub_uint64_t data_size; /* Sectors in this device that can be used for data. */
+ grub_uint64_t super_offset; /* Sector start of this superblock. */
+ grub_uint64_t recovery_offset; /* Sectors before this offset (from data_offset) have been recovered. */
+ grub_uint32_t dev_number; /* Permanent identifier of this device - not role in raid. */
+ grub_uint32_t cnt_corrected_read; /* Number of read errors that were corrected by re-writing. */
+ grub_uint8_t device_uuid[16]; /* User-space setable, ignored by kernel. */
+ grub_uint8_t devflags; /* Per-device flags. Only one defined... */
+ grub_uint8_t pad2[64 - 57]; /* Set to 0 when writing. */
+
+ /* Array state information - 64 bytes. */
+ grub_uint64_t utime; /* 40 bits second, 24 btes microseconds. */
+ grub_uint64_t events; /* Incremented when superblock updated. */
+ grub_uint64_t resync_offset; /* Data before this offset (from data_offset) known to be in sync. */
+ grub_uint32_t sb_csum; /* Checksum upto devs[max_dev]. */
+ grub_uint32_t max_dev; /* Size of devs[] array to consider. */
+ grub_uint8_t pad3[64 - 32]; /* Set to 0 when writing. */
+
+ /* Device state information. Indexed by dev_number.
+ * 2 bytes per device.
+ * Note there are no per-device state flags. State information is rolled
+ * into the 'roles' value. If a device is spare or faulty, then it doesn't
+ * have a meaningful role.
+ */
+ grub_uint16_t dev_roles[0]; /* Role in array, or 0xffff for a spare, or 0xfffe for faulty. */
+};
+/* Could be __attribute__ ((packed)), but since all members in this struct
+ are already appropriately aligned, we can omit this and avoid suboptimal
+ assembly in some cases. */
+
+#define WriteMostly1 1 /* Mask for writemostly flag in above devflags. */
+
+static grub_err_t
+grub_mdraid_detect (grub_disk_t disk, struct grub_raid_array *array,
+ grub_disk_addr_t *start_sector)
+{
+ grub_disk_addr_t sector = 0;
+ grub_uint64_t size;
+ struct grub_raid_super_1x sb;
+ grub_uint8_t minor_version;
+
+ size = grub_disk_get_size (disk);
+
+ /* Check for an 1.x superblock.
+ * It's always aligned to a 4K boundary
+ * and depending on the minor version it can be:
+ * 0: At least 8K, but less than 12K, from end of device
+ * 1: At start of device
+ * 2: 4K from start of device.
+ */
+
+ for (minor_version = 0; minor_version < 3; ++minor_version)
+ {
+ if (size == GRUB_DISK_SIZE_UNKNOWN && minor_version == 0)
+ continue;
+
+ switch (minor_version)
+ {
+ case 0:
+ sector = (size - 8 * 2) & ~(4 * 2 - 1);
+ break;
+ case 1:
+ sector = 0;
+ break;
+ case 2:
+ sector = 4 * 2;
+ break;
+ }
+
+ if (grub_disk_read (disk, sector, 0, sizeof (struct grub_raid_super_1x),
+ &sb))
+ return grub_errno;
+
+ if (grub_le_to_cpu32 (sb.magic) != SB_MAGIC
+ || grub_le_to_cpu64 (sb.super_offset) != sector)
+ continue;
+
+ {
+ grub_uint64_t sb_size;
+ struct grub_raid_super_1x *real_sb;
+ grub_uint32_t level;
+
+ if (grub_le_to_cpu32 (sb.major_version) != 1)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "Unsupported RAID version: %d",
+ grub_le_to_cpu32 (sb.major_version));
+
+ level = grub_le_to_cpu32 (sb.level);
+
+ /* Multipath. */
+ if ((int) level == -4)
+ level = 1;
+
+ if (level != 0 && level != 1 && level != 4 &&
+ level != 5 && level != 6 && level != 10)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "Unsupported RAID level: %d", sb.level);
+
+ /* 1.x superblocks don't have a fixed size on disk. So we have to
+ read it again now that we now the max device count. */
+ sb_size = sizeof (struct grub_raid_super_1x)
+ + 2 * grub_le_to_cpu32 (sb.max_dev);
+ real_sb = grub_malloc (sb_size);
+ if (! real_sb)
+ return grub_errno;
+
+ if (grub_disk_read (disk, sector, 0, sb_size, real_sb))
+ {
+ grub_free (real_sb);
+ return grub_errno;
+ }
+
+ array->name = grub_strdup (real_sb->set_name);
+ if (! array->name)
+ {
+ grub_free (real_sb);
+ return grub_errno;
+ }
+
+ array->number = 0;
+ array->level = grub_le_to_cpu32 (real_sb->level);
+ array->layout = grub_le_to_cpu32 (real_sb->layout);
+ array->total_devs = grub_le_to_cpu32 (real_sb->raid_disks);
+ if (real_sb->size)
+ array->disk_size = grub_le_to_cpu64 (real_sb->size);
+ else
+ array->disk_size = grub_le_to_cpu64 (real_sb->data_size);
+ array->chunk_size = grub_le_to_cpu32 (real_sb->chunksize);
+
+ if (grub_le_to_cpu32 (real_sb->dev_number) >=
+ grub_le_to_cpu32 (real_sb->max_dev))
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ "spares aren't implemented");
+
+ array->index = grub_le_to_cpu16
+ (real_sb->dev_roles[grub_le_to_cpu32 (real_sb->dev_number)]);
+ if (array->index >= array->total_devs)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ "spares aren't implemented");
+ array->uuid_len = 16;
+ array->uuid = grub_malloc (16);
+ if (!array->uuid)
+ {
+ grub_free (real_sb);
+ return grub_errno;
+ }
+
+ grub_memcpy (array->uuid, real_sb->set_uuid, 16);
+
+ *start_sector = grub_le_to_cpu64 (real_sb->data_offset);
+
+ grub_free (real_sb);
+ return 0;
+ }
+ }
+
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "not 1.x raid");
+}
+
+static struct grub_raid grub_mdraid_dev = {
+ .name = "mdraid1x",
+ .detect = grub_mdraid_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT (mdraid1x)
+{
+ grub_raid_register (&grub_mdraid_dev);
+}
+
+GRUB_MOD_FINI (mdraid1x)
+{
+ grub_raid_unregister (&grub_mdraid_dev);
+}
diff --git a/grub-core/disk/mdraid_linux.c b/grub-core/disk/mdraid_linux.c
new file mode 100644
index 0000000..0e2d850
--- /dev/null
+++ b/grub-core/disk/mdraid_linux.c
@@ -0,0 +1,248 @@
+/* mdraid_linux.c - module to handle Linux Software RAID. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+
+/* Linux RAID on disk structures and constants,
+ copied from include/linux/raid/md_p.h. */
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define RESERVED_BYTES (64 * 1024)
+#define RESERVED_SECTORS (RESERVED_BYTES / 512)
+
+#define NEW_SIZE_SECTORS(x) ((x & ~(RESERVED_SECTORS - 1)) \
+ - RESERVED_SECTORS)
+
+#define SB_BYTES 4096
+#define SB_WORDS (SB_BYTES / 4)
+#define SB_SECTORS (SB_BYTES / 512)
+
+/*
+ * The following are counted in 32-bit words
+ */
+#define SB_GENERIC_OFFSET 0
+
+#define SB_PERSONALITY_OFFSET 64
+#define SB_DISKS_OFFSET 128
+#define SB_DESCRIPTOR_OFFSET 992
+
+#define SB_GENERIC_CONSTANT_WORDS 32
+#define SB_GENERIC_STATE_WORDS 32
+#define SB_GENERIC_WORDS (SB_GENERIC_CONSTANT_WORDS + \
+ SB_GENERIC_STATE_WORDS)
+
+#define SB_PERSONALITY_WORDS 64
+#define SB_DESCRIPTOR_WORDS 32
+#define SB_DISKS 27
+#define SB_DISKS_WORDS (SB_DISKS * SB_DESCRIPTOR_WORDS)
+
+#define SB_RESERVED_WORDS (1024 \
+ - SB_GENERIC_WORDS \
+ - SB_PERSONALITY_WORDS \
+ - SB_DISKS_WORDS \
+ - SB_DESCRIPTOR_WORDS)
+
+#define SB_EQUAL_WORDS (SB_GENERIC_WORDS \
+ + SB_PERSONALITY_WORDS \
+ + SB_DISKS_WORDS)
+
+/*
+ * Device "operational" state bits
+ */
+#define DISK_FAULTY 0
+#define DISK_ACTIVE 1
+#define DISK_SYNC 2
+#define DISK_REMOVED 3
+
+#define DISK_WRITEMOSTLY 9
+
+#define SB_MAGIC 0xa92b4efc
+
+/*
+ * Superblock state bits
+ */
+#define SB_CLEAN 0
+#define SB_ERRORS 1
+
+#define SB_BITMAP_PRESENT 8
+
+struct grub_raid_disk_09
+{
+ grub_uint32_t number; /* Device number in the entire set. */
+ grub_uint32_t major; /* Device major number. */
+ grub_uint32_t minor; /* Device minor number. */
+ grub_uint32_t raid_disk; /* The role of the device in the raid set. */
+ grub_uint32_t state; /* Operational state. */
+ grub_uint32_t reserved[SB_DESCRIPTOR_WORDS - 5];
+};
+
+struct grub_raid_super_09
+{
+ /*
+ * Constant generic information
+ */
+ grub_uint32_t md_magic; /* MD identifier. */
+ grub_uint32_t major_version; /* Major version. */
+ grub_uint32_t minor_version; /* Minor version. */
+ grub_uint32_t patch_version; /* Patchlevel version. */
+ grub_uint32_t gvalid_words; /* Number of used words in this section. */
+ grub_uint32_t set_uuid0; /* Raid set identifier. */
+ grub_uint32_t ctime; /* Creation time. */
+ grub_uint32_t level; /* Raid personality. */
+ grub_uint32_t size; /* Apparent size of each individual disk. */
+ grub_uint32_t nr_disks; /* Total disks in the raid set. */
+ grub_uint32_t raid_disks; /* Disks in a fully functional raid set. */
+ grub_uint32_t md_minor; /* Preferred MD minor device number. */
+ grub_uint32_t not_persistent; /* Does it have a persistent superblock. */
+ grub_uint32_t set_uuid1; /* Raid set identifier #2. */
+ grub_uint32_t set_uuid2; /* Raid set identifier #3. */
+ grub_uint32_t set_uuid3; /* Raid set identifier #4. */
+ grub_uint32_t gstate_creserved[SB_GENERIC_CONSTANT_WORDS - 16];
+
+ /*
+ * Generic state information
+ */
+ grub_uint32_t utime; /* Superblock update time. */
+ grub_uint32_t state; /* State bits (clean, ...). */
+ grub_uint32_t active_disks; /* Number of currently active disks. */
+ grub_uint32_t working_disks; /* Number of working disks. */
+ grub_uint32_t failed_disks; /* Number of failed disks. */
+ grub_uint32_t spare_disks; /* Number of spare disks. */
+ grub_uint32_t sb_csum; /* Checksum of the whole superblock. */
+ grub_uint64_t events; /* Superblock update count. */
+ grub_uint64_t cp_events; /* Checkpoint update count. */
+ grub_uint32_t recovery_cp; /* Recovery checkpoint sector count. */
+ grub_uint32_t gstate_sreserved[SB_GENERIC_STATE_WORDS - 12];
+
+ /*
+ * Personality information
+ */
+ grub_uint32_t layout; /* The array's physical layout. */
+ grub_uint32_t chunk_size; /* Chunk size in bytes. */
+ grub_uint32_t root_pv; /* LV root PV. */
+ grub_uint32_t root_block; /* LV root block. */
+ grub_uint32_t pstate_reserved[SB_PERSONALITY_WORDS - 4];
+
+ /*
+ * Disks information
+ */
+ struct grub_raid_disk_09 disks[SB_DISKS];
+
+ /*
+ * Reserved
+ */
+ grub_uint32_t reserved[SB_RESERVED_WORDS];
+
+ /*
+ * Active descriptor
+ */
+ struct grub_raid_disk_09 this_disk;
+} __attribute__ ((packed));
+
+static grub_err_t
+grub_mdraid_detect (grub_disk_t disk, struct grub_raid_array *array,
+ grub_disk_addr_t *start_sector)
+{
+ grub_disk_addr_t sector;
+ grub_uint64_t size;
+ struct grub_raid_super_09 sb;
+ grub_uint32_t *uuid;
+ grub_uint32_t level;
+
+ /* The sector where the mdraid 0.90 superblock is stored, if available. */
+ size = grub_disk_get_size (disk);
+ if (size == GRUB_DISK_SIZE_UNKNOWN)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "not 0.9x raid");
+ sector = NEW_SIZE_SECTORS (size);
+
+ if (grub_disk_read (disk, sector, 0, SB_BYTES, &sb))
+ return grub_errno;
+
+ /* Look whether there is a mdraid 0.90 superblock. */
+ if (grub_le_to_cpu32 (sb.md_magic) != SB_MAGIC)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, "not 0.9x raid");
+
+ if (grub_le_to_cpu32 (sb.major_version) != 0
+ || grub_le_to_cpu32 (sb.minor_version) != 90)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID version: %d.%d",
+ grub_le_to_cpu32 (sb.major_version),
+ grub_le_to_cpu32 (sb.minor_version));
+
+ /* FIXME: Check the checksum. */
+
+ level = grub_le_to_cpu32 (sb.level);
+ /* Multipath. */
+ if ((int) level == -4)
+ level = 1;
+
+ if (level != 0 && level != 1 && level != 4 &&
+ level != 5 && level != 6 && level != 10)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level: %d", level);
+ if (grub_le_to_cpu32 (sb.this_disk.number) == 0xffff
+ || grub_le_to_cpu32 (sb.this_disk.number) == 0xfffe)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ "spares aren't implemented");
+
+ array->name = NULL;
+ array->number = grub_le_to_cpu32 (sb.md_minor);
+ array->level = level;
+ array->layout = grub_le_to_cpu32 (sb.layout);
+ array->total_devs = grub_le_to_cpu32 (sb.raid_disks);
+ array->disk_size = (sb.size) ? grub_le_to_cpu32 (sb.size) * 2 : sector;
+ array->chunk_size = grub_le_to_cpu32 (sb.chunk_size) >> 9;
+ array->index = grub_le_to_cpu32 (sb.this_disk.number);
+ array->uuid_len = 16;
+ array->uuid = grub_malloc (16);
+ if (!array->uuid)
+ return grub_errno;
+
+ uuid = (grub_uint32_t *) array->uuid;
+ uuid[0] = grub_swap_bytes32 (sb.set_uuid0);
+ uuid[1] = grub_swap_bytes32 (sb.set_uuid1);
+ uuid[2] = grub_swap_bytes32 (sb.set_uuid2);
+ uuid[3] = grub_swap_bytes32 (sb.set_uuid3);
+
+ *start_sector = 0;
+
+ return 0;
+}
+
+static struct grub_raid grub_mdraid_dev = {
+ .name = "mdraid09",
+ .detect = grub_mdraid_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT (mdraid09)
+{
+ grub_raid_register (&grub_mdraid_dev);
+}
+
+GRUB_MOD_FINI (mdraid09)
+{
+ grub_raid_unregister (&grub_mdraid_dev);
+}
diff --git a/grub-core/disk/memdisk.c b/grub-core/disk/memdisk.c
new file mode 100644
index 0000000..114ac0d
--- /dev/null
+++ b/grub-core/disk/memdisk.c
@@ -0,0 +1,117 @@
+/* memdisk.c - Access embedded memory disk. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/kernel.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/types.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static char *memdisk_addr;
+static grub_off_t memdisk_size = 0;
+
+static int
+grub_memdisk_iterate (int (*hook) (const char *name))
+{
+ return hook ("memdisk");
+}
+
+static grub_err_t
+grub_memdisk_open (const char *name, grub_disk_t disk)
+{
+ if (grub_strcmp (name, "memdisk"))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a memdisk");
+
+ disk->total_sectors = memdisk_size / GRUB_DISK_SECTOR_SIZE;
+ disk->id = (unsigned long) "mdsk";
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_memdisk_close (grub_disk_t disk __attribute((unused)))
+{
+}
+
+static grub_err_t
+grub_memdisk_read (grub_disk_t disk __attribute((unused)), grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_memcpy (buf, memdisk_addr + (sector << GRUB_DISK_SECTOR_BITS), size << GRUB_DISK_SECTOR_BITS);
+ return 0;
+}
+
+static grub_err_t
+grub_memdisk_write (grub_disk_t disk __attribute((unused)), grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ grub_memcpy (memdisk_addr + (sector << GRUB_DISK_SECTOR_BITS), buf, size << GRUB_DISK_SECTOR_BITS);
+ return 0;
+}
+
+static struct grub_disk_dev grub_memdisk_dev =
+ {
+ .name = "memdisk",
+ .id = GRUB_DISK_DEVICE_MEMDISK_ID,
+ .iterate = grub_memdisk_iterate,
+ .open = grub_memdisk_open,
+ .close = grub_memdisk_close,
+ .read = grub_memdisk_read,
+ .write = grub_memdisk_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(memdisk)
+{
+ auto int hook (struct grub_module_header *);
+ int hook (struct grub_module_header *header)
+ {
+ if (header->type == OBJ_TYPE_MEMDISK)
+ {
+ char *memdisk_orig_addr;
+ memdisk_orig_addr = (char *) header + sizeof (struct grub_module_header);
+
+ grub_dprintf ("memdisk", "Found memdisk image at %p\n", memdisk_orig_addr);
+
+ memdisk_size = header->size - sizeof (struct grub_module_header);
+ memdisk_addr = grub_malloc (memdisk_size);
+
+ grub_dprintf ("memdisk", "Copying memdisk image to dynamic memory\n");
+ grub_memmove (memdisk_addr, memdisk_orig_addr, memdisk_size);
+
+ grub_disk_dev_register (&grub_memdisk_dev);
+ return 1;
+ }
+
+ return 0;
+ }
+
+ grub_module_iterate (hook);
+}
+
+GRUB_MOD_FINI(memdisk)
+{
+ if (! memdisk_size)
+ return;
+ grub_free (memdisk_addr);
+ grub_disk_dev_unregister (&grub_memdisk_dev);
+}
diff --git a/grub-core/disk/raid.c b/grub-core/disk/raid.c
new file mode 100644
index 0000000..946e6d2
--- /dev/null
+++ b/grub-core/disk/raid.c
@@ -0,0 +1,812 @@
+/* raid.c - module to read RAID arrays. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+#ifdef GRUB_UTIL
+#include <grub/util/misc.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Linked list of RAID arrays. */
+static struct grub_raid_array *array_list;
+grub_raid5_recover_func_t grub_raid5_recover_func;
+grub_raid6_recover_func_t grub_raid6_recover_func;
+
+
+static char
+grub_is_array_readable (struct grub_raid_array *array)
+{
+ switch (array->level)
+ {
+ case 0:
+ if (array->nr_devs == array->total_devs)
+ return 1;
+ break;
+
+ case 1:
+ if (array->nr_devs >= 1)
+ return 1;
+ break;
+
+ case 4:
+ case 5:
+ case 6:
+ case 10:
+ {
+ unsigned int n;
+
+ if (array->level == 10)
+ {
+ n = array->layout & 0xFF;
+ if (n == 1)
+ n = (array->layout >> 8) & 0xFF;
+
+ n--;
+ }
+ else
+ n = array->level / 3;
+
+ if (array->nr_devs >= array->total_devs - n)
+ return 1;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int
+grub_raid_iterate (int (*hook) (const char *name))
+{
+ struct grub_raid_array *array;
+
+ for (array = array_list; array != NULL; array = array->next)
+ {
+ if (grub_is_array_readable (array))
+ if (hook (array->name))
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef GRUB_UTIL
+static grub_disk_memberlist_t
+grub_raid_memberlist (grub_disk_t disk)
+{
+ struct grub_raid_array *array = disk->data;
+ grub_disk_memberlist_t list = NULL, tmp;
+ unsigned int i;
+
+ for (i = 0; i < array->total_devs; i++)
+ if (array->members[i].device)
+ {
+ tmp = grub_malloc (sizeof (*tmp));
+ tmp->disk = array->members[i].device;
+ tmp->next = list;
+ list = tmp;
+ }
+
+ return list;
+}
+
+static const char *
+grub_raid_getname (struct grub_disk *disk)
+{
+ struct grub_raid_array *array = disk->data;
+
+ return array->driver->name;
+}
+#endif
+
+static inline int
+ascii2hex (char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return 0;
+}
+
+static grub_err_t
+grub_raid_open (const char *name, grub_disk_t disk)
+{
+ struct grub_raid_array *array;
+ unsigned n;
+
+ if (grub_memcmp (name, "mduuid/", sizeof ("mduuid/") - 1) == 0)
+ {
+ const char *uuidstr = name + sizeof ("mduuid/") - 1;
+ grub_size_t uuid_len = grub_strlen (uuidstr) / 2;
+ grub_uint8_t uuidbin[uuid_len];
+ unsigned i;
+ for (i = 0; i < uuid_len; i++)
+ uuidbin[i] = ascii2hex (uuidstr[2 * i + 1])
+ | (ascii2hex (uuidstr[2 * i]) << 4);
+
+ for (array = array_list; array != NULL; array = array->next)
+ {
+ if (uuid_len == (unsigned) array->uuid_len
+ && grub_memcmp (uuidbin, array->uuid, uuid_len) == 0)
+ if (grub_is_array_readable (array))
+ break;
+ }
+ }
+ else
+ for (array = array_list; array != NULL; array = array->next)
+ {
+ if (!grub_strcmp (array->name, name))
+ if (grub_is_array_readable (array))
+ break;
+ }
+
+ if (!array)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown RAID device %s",
+ name);
+
+ disk->id = array->number;
+ disk->data = array;
+
+ grub_dprintf ("raid", "%s: total_devs=%d, disk_size=%lld\n", name,
+ array->total_devs, (unsigned long long) array->disk_size);
+
+ switch (array->level)
+ {
+ case 1:
+ disk->total_sectors = array->disk_size;
+ break;
+
+ case 10:
+ n = array->layout & 0xFF;
+ if (n == 1)
+ n = (array->layout >> 8) & 0xFF;
+
+ disk->total_sectors = grub_divmod64 (array->total_devs *
+ array->disk_size,
+ n, 0);
+ break;
+
+ case 0:
+ case 4:
+ case 5:
+ case 6:
+ n = array->level / 3;
+
+ disk->total_sectors = (array->total_devs - n) * array->disk_size;
+ break;
+ }
+
+ grub_dprintf ("raid", "%s: level=%d, total_sectors=%lld\n", name,
+ array->level, (unsigned long long) disk->total_sectors);
+
+ return 0;
+}
+
+static void
+grub_raid_close (grub_disk_t disk __attribute ((unused)))
+{
+ return;
+}
+
+void
+grub_raid_block_xor (char *buf1, const char *buf2, int size)
+{
+ grub_size_t *p1;
+ const grub_size_t *p2;
+
+ p1 = (grub_size_t *) buf1;
+ p2 = (const grub_size_t *) buf2;
+ size /= GRUB_CPU_SIZEOF_VOID_P;
+
+ while (size)
+ {
+ *(p1++) ^= *(p2++);
+ size--;
+ }
+}
+
+static grub_err_t
+grub_raid_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ struct grub_raid_array *array = disk->data;
+ grub_err_t err = 0;
+
+ switch (array->level)
+ {
+ case 0:
+ case 1:
+ case 10:
+ {
+ grub_disk_addr_t read_sector, far_ofs;
+ grub_uint32_t disknr, b, near, far, ofs;
+
+ read_sector = grub_divmod64 (sector, array->chunk_size, &b);
+ far = ofs = near = 1;
+ far_ofs = 0;
+
+ if (array->level == 1)
+ near = array->total_devs;
+ else if (array->level == 10)
+ {
+ near = array->layout & 0xFF;
+ far = (array->layout >> 8) & 0xFF;
+ if (array->layout >> 16)
+ {
+ ofs = far;
+ far_ofs = 1;
+ }
+ else
+ far_ofs = grub_divmod64 (array->disk_size,
+ far * array->chunk_size, 0);
+
+ far_ofs *= array->chunk_size;
+ }
+
+ read_sector = grub_divmod64 (read_sector * near, array->total_devs,
+ &disknr);
+
+ ofs *= array->chunk_size;
+ read_sector *= ofs;
+
+ while (1)
+ {
+ grub_size_t read_size;
+ unsigned int i, j;
+
+ read_size = array->chunk_size - b;
+ if (read_size > size)
+ read_size = size;
+
+ for (i = 0; i < near; i++)
+ {
+ unsigned int k;
+
+ k = disknr;
+ for (j = 0; j < far; j++)
+ {
+ if (array->members[k].device)
+ {
+ if (grub_errno == GRUB_ERR_READ_ERROR)
+ grub_errno = GRUB_ERR_NONE;
+
+ err = grub_disk_read (array->members[k].device,
+ array->members[k].start_sector +
+ read_sector + j * far_ofs + b,
+ 0,
+ read_size << GRUB_DISK_SECTOR_BITS,
+ buf);
+ if (! err)
+ break;
+ else if (err != GRUB_ERR_READ_ERROR)
+ return err;
+ }
+ else
+ err = grub_error (GRUB_ERR_READ_ERROR,
+ "disk missing");
+
+ k++;
+ if (k == array->total_devs)
+ k = 0;
+ }
+
+ if (! err)
+ break;
+
+ disknr++;
+ if (disknr == array->total_devs)
+ {
+ disknr = 0;
+ read_sector += ofs;
+ }
+ }
+
+ if (err)
+ return err;
+
+ buf += read_size << GRUB_DISK_SECTOR_BITS;
+ size -= read_size;
+ if (! size)
+ break;
+
+ b = 0;
+ disknr += (near - i);
+ while (disknr >= array->total_devs)
+ {
+ disknr -= array->total_devs;
+ read_sector += ofs;
+ }
+ }
+ break;
+ }
+
+ case 4:
+ case 5:
+ case 6:
+ {
+ grub_disk_addr_t read_sector;
+ grub_uint32_t b, p, n, disknr, e;
+
+ /* n = 1 for level 4 and 5, 2 for level 6. */
+ n = array->level / 3;
+
+ /* Find the first sector to read. */
+ read_sector = grub_divmod64 (sector, array->chunk_size, &b);
+ read_sector = grub_divmod64 (read_sector, array->total_devs - n,
+ &disknr);
+ if (array->level >= 5)
+ {
+ grub_divmod64 (read_sector, array->total_devs, &p);
+
+ if (! (array->layout & GRUB_RAID_LAYOUT_RIGHT_MASK))
+ p = array->total_devs - 1 - p;
+
+ if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ disknr += p + n;
+ }
+ else
+ {
+ grub_uint32_t q;
+
+ q = p + (n - 1);
+ if (q >= array->total_devs)
+ q -= array->total_devs;
+
+ if (disknr >= p)
+ disknr += n;
+ else if (disknr >= q)
+ disknr += q + 1;
+ }
+
+ if (disknr >= array->total_devs)
+ disknr -= array->total_devs;
+ }
+ else
+ p = array->total_devs - n;
+
+ read_sector *= array->chunk_size;
+
+ while (1)
+ {
+ grub_size_t read_size;
+ int next_level;
+
+ read_size = array->chunk_size - b;
+ if (read_size > size)
+ read_size = size;
+
+ e = 0;
+ if (array->members[disknr].device)
+ {
+ /* Reset read error. */
+ if (grub_errno == GRUB_ERR_READ_ERROR)
+ grub_errno = GRUB_ERR_NONE;
+
+ err = grub_disk_read (array->members[disknr].device,
+ array->members[disknr].start_sector +
+ read_sector + b, 0,
+ read_size << GRUB_DISK_SECTOR_BITS,
+ buf);
+
+ if ((err) && (err != GRUB_ERR_READ_ERROR))
+ break;
+ e++;
+ }
+ else
+ err = GRUB_ERR_READ_ERROR;
+
+ if (err)
+ {
+ if (array->nr_devs < array->total_devs - n + e)
+ break;
+
+ grub_errno = GRUB_ERR_NONE;
+ if (array->level == 6)
+ {
+ err = ((grub_raid6_recover_func) ?
+ (*grub_raid6_recover_func) (array, disknr, p,
+ buf, read_sector + b,
+ read_size) :
+ grub_error (GRUB_ERR_BAD_DEVICE,
+ "raid6rec is not loaded"));
+ }
+ else
+ {
+ err = ((grub_raid5_recover_func) ?
+ (*grub_raid5_recover_func) (array, disknr,
+ buf, read_sector + b,
+ read_size) :
+ grub_error (GRUB_ERR_BAD_DEVICE,
+ "raid5rec is not loaded"));
+ }
+
+ if (err)
+ break;
+ }
+
+ buf += read_size << GRUB_DISK_SECTOR_BITS;
+ size -= read_size;
+ if (! size)
+ break;
+
+ b = 0;
+ disknr++;
+
+ if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ if (disknr == array->total_devs)
+ disknr = 0;
+
+ next_level = (disknr == p);
+ }
+ else
+ {
+ if (disknr == p)
+ disknr += n;
+
+ next_level = (disknr >= array->total_devs);
+ }
+
+ if (next_level)
+ {
+ read_sector += array->chunk_size;
+
+ if (array->level >= 5)
+ {
+ if (array->layout & GRUB_RAID_LAYOUT_RIGHT_MASK)
+ p = (p == array->total_devs - 1) ? 0 : p + 1;
+ else
+ p = (p == 0) ? array->total_devs - 1 : p - 1;
+
+ if (array->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ disknr = p + n;
+ if (disknr >= array->total_devs)
+ disknr -= array->total_devs;
+ }
+ else
+ {
+ disknr -= array->total_devs;
+ if (disknr == p)
+ disknr += n;
+ }
+ }
+ else
+ disknr = 0;
+ }
+ }
+ }
+ break;
+ }
+
+ return err;
+}
+
+static grub_err_t
+grub_raid_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+static grub_err_t
+insert_array (grub_disk_t disk, struct grub_raid_array *new_array,
+ grub_disk_addr_t start_sector, const char *scanner_name,
+ grub_raid_t raid __attribute__ ((unused)))
+{
+ struct grub_raid_array *array = 0, *p;
+
+ /* See whether the device is part of an array we have already seen a
+ device from. */
+ for (p = array_list; p != NULL; p = p->next)
+ if ((p->uuid_len == new_array->uuid_len) &&
+ (! grub_memcmp (p->uuid, new_array->uuid, p->uuid_len)))
+ {
+ grub_free (new_array->uuid);
+ array = p;
+
+ /* Do some checks before adding the device to the array. */
+
+ if (new_array->index >= array->allocated_devs)
+ {
+ void *tmp;
+ unsigned int newnum = 2 * (new_array->index + 1);
+ tmp = grub_realloc (array->members, newnum
+ * sizeof (array->members[0]));
+ if (!tmp)
+ return grub_errno;
+ array->members = tmp;
+ grub_memset (array->members + array->allocated_devs,
+ 0, (newnum - array->allocated_devs)
+ * sizeof (array->members[0]));
+ array->allocated_devs = newnum;
+ }
+
+ /* FIXME: Check whether the update time of the superblocks are
+ the same. */
+
+ if (array->total_devs == array->nr_devs)
+ /* We found more members of the array than the array
+ actually has according to its superblock. This shouldn't
+ happen normally. */
+ return grub_error (GRUB_ERR_BAD_DEVICE,
+ "superfluous RAID member (%d found)",
+ array->total_devs);
+
+ if (array->members[new_array->index].device != NULL)
+ /* We found multiple devices with the same number. Again,
+ this shouldn't happen. */
+ return grub_error (GRUB_ERR_BAD_DEVICE,
+ "found two disks with the index %d for RAID %s",
+ new_array->index, array->name);
+
+ if (new_array->disk_size < array->disk_size)
+ array->disk_size = new_array->disk_size;
+ break;
+ }
+
+ /* Add an array to the list if we didn't find any. */
+ if (!array)
+ {
+ array = grub_malloc (sizeof (*array));
+ if (!array)
+ {
+ grub_free (new_array->uuid);
+ return grub_errno;
+ }
+
+ *array = *new_array;
+ array->nr_devs = 0;
+#ifdef GRUB_UTIL
+ array->driver = raid;
+#endif
+ array->allocated_devs = 32;
+ if (new_array->index >= array->allocated_devs)
+ array->allocated_devs = 2 * (new_array->index + 1);
+
+ array->members = grub_zalloc (array->allocated_devs
+ * sizeof (array->members[0]));
+
+ if (!array->members)
+ {
+ grub_free (new_array->uuid);
+ return grub_errno;
+ }
+
+ if (! array->name)
+ {
+ for (p = array_list; p != NULL; p = p->next)
+ {
+ if (p->number == array->number)
+ break;
+ }
+ }
+
+ if (array->name || p)
+ {
+ /* The number is already in use, so we need to find a new one.
+ (Or, in the case of named arrays, the array doesn't have its
+ own number, but we need one that doesn't clash for use as a key
+ in the disk cache. */
+ int i = array->name ? 0x40000000 : 0;
+
+ while (1)
+ {
+ for (p = array_list; p != NULL; p = p->next)
+ {
+ if (p->number == i)
+ break;
+ }
+
+ if (! p)
+ {
+ /* We found an unused number. */
+ array->number = i;
+ break;
+ }
+
+ i++;
+ }
+ }
+
+ /* mdraid 1.x superblocks have only a name stored not a number.
+ Use it directly as GRUB device. */
+ if (! array->name)
+ {
+ array->name = grub_xasprintf ("md%d", array->number);
+ if (! array->name)
+ {
+ grub_free (array->members);
+ grub_free (array->uuid);
+ grub_free (array);
+
+ return grub_errno;
+ }
+ }
+ else
+ {
+ /* Strip off the homehost if present. */
+ char *colon = grub_strchr (array->name, ':');
+ char *new_name = grub_xasprintf ("md/%s",
+ colon ? colon + 1 : array->name);
+
+ if (! new_name)
+ {
+ grub_free (array->members);
+ grub_free (array->uuid);
+ grub_free (array);
+
+ return grub_errno;
+ }
+
+ grub_free (array->name);
+ array->name = new_name;
+ }
+
+ grub_dprintf ("raid", "Found array %s (%s)\n", array->name,
+ scanner_name);
+#ifdef GRUB_UTIL
+ grub_util_info ("Found array %s (%s)", array->name,
+ scanner_name);
+#endif
+
+ /* Add our new array to the list. */
+ array->next = array_list;
+ array_list = array;
+
+ /* RAID 1 doesn't use a chunksize but code assumes one so set
+ one. */
+ if (array->level == 1)
+ array->chunk_size = 64;
+ }
+
+ /* Add the device to the array. */
+ array->members[new_array->index].device = disk;
+ array->members[new_array->index].start_sector = start_sector;
+ array->nr_devs++;
+
+ return 0;
+}
+
+static grub_raid_t grub_raid_list;
+
+static void
+free_array (void)
+{
+ struct grub_raid_array *array;
+
+ array = array_list;
+ while (array)
+ {
+ struct grub_raid_array *p;
+ unsigned int i;
+
+ p = array;
+ array = array->next;
+
+ for (i = 0; i < p->allocated_devs; i++)
+ if (p->members[i].device)
+ grub_disk_close (p->members[i].device);
+ grub_free (p->members);
+
+ grub_free (p->uuid);
+ grub_free (p->name);
+ grub_free (p);
+ }
+
+ array_list = 0;
+}
+
+void
+grub_raid_register (grub_raid_t raid)
+{
+ auto int hook (const char *name);
+ int hook (const char *name)
+ {
+ grub_disk_t disk;
+ struct grub_raid_array array;
+ grub_disk_addr_t start_sector;
+
+ grub_dprintf ("raid", "Scanning for %s RAID devices on disk %s\n",
+ grub_raid_list->name, name);
+#ifdef GRUB_UTIL
+ grub_util_info ("Scanning for %s RAID devices on disk %s",
+ grub_raid_list->name, name);
+#endif
+
+ disk = grub_disk_open (name);
+ if (!disk)
+ return 0;
+
+ if ((disk->total_sectors != GRUB_ULONG_MAX) &&
+ (! grub_raid_list->detect (disk, &array, &start_sector)) &&
+ (! insert_array (disk, &array, start_sector, grub_raid_list->name,
+ grub_raid_list)))
+ return 0;
+
+ /* This error usually means it's not raid, no need to display
+ it. */
+ if (grub_errno != GRUB_ERR_OUT_OF_RANGE)
+ grub_print_error ();
+
+ grub_errno = GRUB_ERR_NONE;
+
+ grub_disk_close (disk);
+
+ return 0;
+ }
+
+ raid->next = grub_raid_list;
+ grub_raid_list = raid;
+ grub_device_iterate (&hook);
+}
+
+void
+grub_raid_unregister (grub_raid_t raid)
+{
+ grub_raid_t *p, q;
+
+ for (p = &grub_raid_list, q = *p; q; p = &(q->next), q = q->next)
+ if (q == raid)
+ {
+ *p = q->next;
+ break;
+ }
+}
+
+static struct grub_disk_dev grub_raid_dev =
+ {
+ .name = "raid",
+ .id = GRUB_DISK_DEVICE_RAID_ID,
+ .iterate = grub_raid_iterate,
+ .open = grub_raid_open,
+ .close = grub_raid_close,
+ .read = grub_raid_read,
+ .write = grub_raid_write,
+#ifdef GRUB_UTIL
+ .memberlist = grub_raid_memberlist,
+ .raidname = grub_raid_getname,
+#endif
+ .next = 0
+ };
+
+
+GRUB_MOD_INIT(raid)
+{
+ grub_disk_dev_register (&grub_raid_dev);
+}
+
+GRUB_MOD_FINI(raid)
+{
+ grub_disk_dev_unregister (&grub_raid_dev);
+ free_array ();
+}
diff --git a/grub-core/disk/raid5_recover.c b/grub-core/disk/raid5_recover.c
new file mode 100644
index 0000000..c26d05e
--- /dev/null
+++ b/grub-core/disk/raid5_recover.c
@@ -0,0 +1,76 @@
+/* raid5_recover.c - module to recover from faulty RAID4/5 arrays. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_err_t
+grub_raid5_recover (struct grub_raid_array *array, int disknr,
+ char *buf, grub_disk_addr_t sector, int size)
+{
+ char *buf2;
+ int i;
+
+ size <<= GRUB_DISK_SECTOR_BITS;
+ buf2 = grub_malloc (size);
+ if (!buf2)
+ return grub_errno;
+
+ grub_memset (buf, 0, size);
+
+ for (i = 0; i < (int) array->total_devs; i++)
+ {
+ grub_err_t err;
+
+ if (i == disknr)
+ continue;
+
+ err = grub_disk_read (array->members[i].device,
+ array->members[i].start_sector + sector,
+ 0, size, buf2);
+
+ if (err)
+ {
+ grub_free (buf2);
+ return err;
+ }
+
+ grub_raid_block_xor (buf, buf2, size);
+ }
+
+ grub_free (buf2);
+
+ return GRUB_ERR_NONE;
+}
+
+GRUB_MOD_INIT(raid5rec)
+{
+ grub_raid5_recover_func = grub_raid5_recover;
+}
+
+GRUB_MOD_FINI(raid5rec)
+{
+ grub_raid5_recover_func = 0;
+}
diff --git a/grub-core/disk/raid6_recover.c b/grub-core/disk/raid6_recover.c
new file mode 100644
index 0000000..25b50eb
--- /dev/null
+++ b/grub-core/disk/raid6_recover.c
@@ -0,0 +1,230 @@
+/* raid6_recover.c - module to recover from faulty RAID6 arrays. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/raid.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_uint8_t raid6_table1[256][256];
+static grub_uint8_t raid6_table2[256][256];
+
+static void
+grub_raid_block_mul (grub_uint8_t mul, char *buf, int size)
+{
+ int i;
+ grub_uint8_t *p;
+
+ p = (grub_uint8_t *) buf;
+ for (i = 0; i < size; i++, p++)
+ *p = raid6_table1[mul][*p];
+}
+
+static void
+grub_raid6_init_table (void)
+{
+ int i, j;
+
+ for (i = 0; i < 256; i++)
+ raid6_table1[i][1] = raid6_table1[1][i] = i;
+
+ for (i = 2; i < 256; i++)
+ for (j = i; j < 256; j++)
+ {
+ int n;
+ grub_uint8_t c;
+
+ n = i >> 1;
+
+ c = raid6_table1[n][j];
+ c = (c << 1) ^ ((c & 0x80) ? 0x1d : 0);
+ if (i & 1)
+ c ^= j;
+
+ raid6_table1[j][i] = raid6_table1[i][j] = c;
+ }
+
+ raid6_table2[0][0] = 1;
+ for (i = 1; i < 256; i++)
+ raid6_table2[i][i] = raid6_table1[raid6_table2[i - 1][i - 1]][2];
+
+ for (i = 0; i < 254; i++)
+ for (j = 0; j < 254; j++)
+ {
+ grub_uint8_t c, n;
+ int k;
+
+ if (i == j)
+ continue;
+
+ k = i - j;
+ if (k < 0)
+ k += 255;
+
+ c = n = raid6_table2[k][k] ^ 1;
+ for (k = 0; k < 253; k++)
+ c = raid6_table1[c][n];
+
+ raid6_table2[i][j] = raid6_table1[raid6_table2[255 - j][255 - j]][c];
+ }
+}
+
+static grub_err_t
+grub_raid6_recover (struct grub_raid_array *array, int disknr, int p,
+ char *buf, grub_disk_addr_t sector, int size)
+{
+ int i, q, pos;
+ int bad1 = -1, bad2 = -1;
+ char *pbuf = 0, *qbuf = 0;
+
+ size <<= GRUB_DISK_SECTOR_BITS;
+ pbuf = grub_zalloc (size);
+ if (!pbuf)
+ goto quit;
+
+ qbuf = grub_zalloc (size);
+ if (!qbuf)
+ goto quit;
+
+ q = p + 1;
+ if (q == (int) array->total_devs)
+ q = 0;
+
+ pos = q + 1;
+ if (pos == (int) array->total_devs)
+ pos = 0;
+
+ for (i = 0; i < (int) array->total_devs - 2; i++)
+ {
+ if (pos == disknr)
+ bad1 = i;
+ else
+ {
+ if ((array->members[pos].device) &&
+ (! grub_disk_read (array->members[pos].device,
+ array->members[i].start_sector + sector,
+ 0, size, buf)))
+ {
+ grub_raid_block_xor (pbuf, buf, size);
+ grub_raid_block_mul (raid6_table2[i][i], buf, size);
+ grub_raid_block_xor (qbuf, buf, size);
+ }
+ else
+ {
+ /* Too many bad devices */
+ if (bad2 >= 0)
+ goto quit;
+
+ bad2 = i;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+
+ pos++;
+ if (pos == (int) array->total_devs)
+ pos = 0;
+ }
+
+ /* Invalid disknr or p */
+ if (bad1 < 0)
+ goto quit;
+
+ if (bad2 < 0)
+ {
+ /* One bad device */
+ if ((array->members[p].device) &&
+ (! grub_disk_read (array->members[p].device,
+ array->members[i].start_sector + sector,
+ 0, size, buf)))
+ {
+ grub_raid_block_xor (buf, pbuf, size);
+ goto quit;
+ }
+
+ if (! array->members[q].device)
+ {
+ grub_error (GRUB_ERR_READ_ERROR, "not enough disk to restore");
+ goto quit;
+ }
+
+ grub_errno = GRUB_ERR_NONE;
+ if (grub_disk_read (array->members[q].device,
+ array->members[i].start_sector + sector, 0, size, buf))
+ goto quit;
+
+ grub_raid_block_xor (buf, qbuf, size);
+ grub_raid_block_mul (raid6_table2[255 - bad1][255 - bad1], buf,
+ size);
+ }
+ else
+ {
+ /* Two bad devices */
+ grub_uint8_t c;
+
+ if ((! array->members[p].device) || (! array->members[q].device))
+ {
+ grub_error (GRUB_ERR_READ_ERROR, "not enough disk to restore");
+ goto quit;
+ }
+
+ if (grub_disk_read (array->members[p].device,
+ array->members[i].start_sector + sector,
+ 0, size, buf))
+ goto quit;
+
+ grub_raid_block_xor (pbuf, buf, size);
+
+ if (grub_disk_read (array->members[q].device,
+ array->members[i].start_sector + sector,
+ 0, size, buf))
+ goto quit;
+
+ grub_raid_block_xor (qbuf, buf, size);
+
+ c = raid6_table2[bad2][bad1];
+ grub_raid_block_mul (c, qbuf, size);
+
+ c = raid6_table1[raid6_table2[bad2][bad2]][c];
+ grub_raid_block_mul (c, pbuf, size);
+
+ grub_raid_block_xor (pbuf, qbuf, size);
+ grub_memcpy (buf, pbuf, size);
+ }
+
+quit:
+ grub_free (pbuf);
+ grub_free (qbuf);
+
+ return grub_errno;
+}
+
+GRUB_MOD_INIT(raid6rec)
+{
+ grub_raid6_init_table ();
+ grub_raid6_recover_func = grub_raid6_recover;
+}
+
+GRUB_MOD_FINI(raid6rec)
+{
+ grub_raid6_recover_func = 0;
+}
diff --git a/grub-core/disk/scsi.c b/grub-core/disk/scsi.c
new file mode 100644
index 0000000..25f0e3a
--- /dev/null
+++ b/grub-core/disk/scsi.c
@@ -0,0 +1,606 @@
+/* scsi.c - scsi support. */
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/kernel.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/types.h>
+#include <grub/scsi.h>
+#include <grub/scsicmd.h>
+#include <grub/time.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+static grub_scsi_dev_t grub_scsi_dev_list;
+
+void
+grub_scsi_dev_register (grub_scsi_dev_t dev)
+{
+ dev->next = grub_scsi_dev_list;
+ grub_scsi_dev_list = dev;
+}
+
+void
+grub_scsi_dev_unregister (grub_scsi_dev_t dev)
+{
+ grub_scsi_dev_t *p, q;
+
+ for (p = &grub_scsi_dev_list, q = *p; q; p = &(q->next), q = q->next)
+ if (q == dev)
+ {
+ *p = q->next;
+ break;
+ }
+}
+
+
+/* Check result of previous operation. */
+static grub_err_t
+grub_scsi_request_sense (grub_scsi_t scsi)
+{
+ struct grub_scsi_request_sense rs;
+ struct grub_scsi_request_sense_data rsd;
+ grub_err_t err;
+
+ rs.opcode = grub_scsi_cmd_request_sense;
+ rs.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ rs.reserved1 = 0;
+ rs.reserved2 = 0;
+ rs.alloc_length = 0x12; /* XXX: Hardcoded for now */
+ rs.control = 0;
+ grub_memset (rs.pad, 0, sizeof(rs.pad));
+
+ err = scsi->dev->read (scsi, sizeof (rs), (char *) &rs,
+ sizeof (rsd), (char *) &rsd);
+ if (err)
+ return err;
+
+ return GRUB_ERR_NONE;
+}
+/* Self commenting... */
+static grub_err_t
+grub_scsi_test_unit_ready (grub_scsi_t scsi)
+{
+ struct grub_scsi_test_unit_ready tur;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ tur.opcode = grub_scsi_cmd_test_unit_ready;
+ tur.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ tur.reserved1 = 0;
+ tur.reserved2 = 0;
+ tur.reserved3 = 0;
+ tur.control = 0;
+ grub_memset (tur.pad, 0, sizeof(tur.pad));
+
+ err = scsi->dev->read (scsi, sizeof (tur), (char *) &tur,
+ 0, NULL);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ if (err)
+ return err;
+
+ return GRUB_ERR_NONE;
+}
+
+/* Determine if the device is removable and the type of the device
+ SCSI. */
+static grub_err_t
+grub_scsi_inquiry (grub_scsi_t scsi)
+{
+ struct grub_scsi_inquiry iq;
+ struct grub_scsi_inquiry_data iqd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ iq.opcode = grub_scsi_cmd_inquiry;
+ iq.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ iq.page = 0;
+ iq.reserved = 0;
+ iq.alloc_length = 0x24; /* XXX: Hardcoded for now */
+ iq.control = 0;
+ grub_memset (iq.pad, 0, sizeof(iq.pad));
+
+ err = scsi->dev->read (scsi, sizeof (iq), (char *) &iq,
+ sizeof (iqd), (char *) &iqd);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ if (err)
+ return err;
+
+ scsi->devtype = iqd.devtype & GRUB_SCSI_DEVTYPE_MASK;
+ scsi->removable = iqd.rmb >> GRUB_SCSI_REMOVABLE_BIT;
+
+ return GRUB_ERR_NONE;
+}
+
+/* Read the capacity and block size of SCSI. */
+static grub_err_t
+grub_scsi_read_capacity (grub_scsi_t scsi)
+{
+ struct grub_scsi_read_capacity rc;
+ struct grub_scsi_read_capacity_data rcd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ rc.opcode = grub_scsi_cmd_read_capacity;
+ rc.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ rc.logical_block_addr = 0;
+ rc.reserved1 = 0;
+ rc.reserved2 = 0;
+ rc.PMI = 0;
+ rc.control = 0;
+ rc.pad = 0;
+
+ err = scsi->dev->read (scsi, sizeof (rc), (char *) &rc,
+ sizeof (rcd), (char *) &rcd);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+/* err_sense is ignored for now and Request Sense Data also... */
+
+ if (err)
+ return err;
+
+ scsi->size = grub_be_to_cpu32 (rcd.size);
+ scsi->blocksize = grub_be_to_cpu32 (rcd.blocksize);
+
+ return GRUB_ERR_NONE;
+}
+
+/* Send a SCSI request for DISK: read SIZE sectors starting with
+ sector SECTOR to BUF. */
+static grub_err_t
+grub_scsi_read10 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_read10 rd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ rd.opcode = grub_scsi_cmd_read10;
+ rd.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ rd.lba = grub_cpu_to_be32 (sector);
+ rd.reserved = 0;
+ rd.size = grub_cpu_to_be16 (size);
+ rd.reserved2 = 0;
+ rd.pad = 0;
+
+ err = scsi->dev->read (scsi, sizeof (rd), (char *) &rd, size * scsi->blocksize, buf);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ return err;
+}
+
+/* Send a SCSI request for DISK: read SIZE sectors starting with
+ sector SECTOR to BUF. */
+static grub_err_t
+grub_scsi_read12 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_read12 rd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ rd.opcode = grub_scsi_cmd_read12;
+ rd.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ rd.lba = grub_cpu_to_be32 (sector);
+ rd.size = grub_cpu_to_be32 (size);
+ rd.reserved = 0;
+ rd.control = 0;
+
+ err = scsi->dev->read (scsi, sizeof (rd), (char *) &rd, size * scsi->blocksize, buf);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ return err;
+}
+
+#if 0
+/* Send a SCSI request for DISK: write the data stored in BUF to SIZE
+ sectors starting with SECTOR. */
+static grub_err_t
+grub_scsi_write10 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_write10 wr;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ wr.opcode = grub_scsi_cmd_write10;
+ wr.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ wr.lba = grub_cpu_to_be32 (sector);
+ wr.reserved = 0;
+ wr.size = grub_cpu_to_be16 (size);
+ wr.reserved2 = 0;
+ wr.pad = 0;
+
+ err = scsi->dev->write (scsi, sizeof (wr), (char *) &wr, size * scsi->blocksize, buf);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ return err;
+}
+
+/* Send a SCSI request for DISK: write the data stored in BUF to SIZE
+ sectors starting with SECTOR. */
+static grub_err_t
+grub_scsi_write12 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_write12 wr;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ wr.opcode = grub_scsi_cmd_write12;
+ wr.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ wr.lba = grub_cpu_to_be32 (sector);
+ wr.size = grub_cpu_to_be32 (size);
+ wr.reserved = 0;
+ wr.control = 0;
+
+ err = scsi->dev->write (scsi, sizeof (wr), (char *) &wr, size * scsi->blocksize, buf);
+
+ /* Each SCSI command should be followed by Request Sense.
+ If not so, many devices STALLs or definitely freezes. */
+ err_sense = grub_scsi_request_sense (scsi);
+ if (err_sense != GRUB_ERR_NONE)
+ grub_errno = err;
+ /* err_sense is ignored for now and Request Sense Data also... */
+
+ return err;
+}
+#endif
+
+
+static int
+grub_scsi_iterate (int (*hook) (const char *name))
+{
+ grub_scsi_dev_t p;
+
+ auto int scsi_iterate (int bus, int luns);
+
+ int scsi_iterate (int bus, int luns)
+ {
+ int i;
+
+ /* In case of a single LUN, just return `usbX'. */
+ if (luns == 1)
+ {
+ char *sname;
+ int ret;
+ sname = grub_xasprintf ("%s%d", p->name, bus);
+ if (!sname)
+ return 1;
+ ret = hook (sname);
+ grub_free (sname);
+ return ret;
+ }
+
+ /* In case of multiple LUNs, every LUN will get a prefix to
+ distinguish it. */
+ for (i = 0; i < luns; i++)
+ {
+ char *sname;
+ int ret;
+ sname = grub_xasprintf ("%s%d%c", p->name, bus, 'a' + i);
+ if (!sname)
+ return 1;
+ ret = hook (sname);
+ grub_free (sname);
+ if (ret)
+ return 1;
+ }
+ return 0;
+ }
+
+ for (p = grub_scsi_dev_list; p; p = p->next)
+ if (p->iterate && (p->iterate) (scsi_iterate))
+ return 1;
+
+ return 0;
+}
+
+static grub_err_t
+grub_scsi_open (const char *name, grub_disk_t disk)
+{
+ grub_scsi_dev_t p;
+ grub_scsi_t scsi;
+ grub_err_t err;
+ int lun, bus;
+ grub_uint64_t maxtime;
+ const char *nameend;
+
+ nameend = name + grub_strlen (name) - 1;
+ /* Try to detect a LUN ('a'-'z'), otherwise just use the first
+ LUN. */
+ if (nameend >= name && *nameend >= 'a' && *nameend <= 'z')
+ {
+ lun = *nameend - 'a';
+ nameend--;
+ }
+ else
+ lun = 0;
+
+ while (nameend >= name && grub_isdigit (*nameend))
+ nameend--;
+
+ if (!nameend[1] || !grub_isdigit (nameend[1]))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a SCSI disk");
+
+ bus = grub_strtoul (nameend + 1, 0, 0);
+
+ scsi = grub_malloc (sizeof (*scsi));
+ if (! scsi)
+ return grub_errno;
+
+ for (p = grub_scsi_dev_list; p; p = p->next)
+ {
+ if (grub_strncmp (p->name, name, nameend - name) != 0)
+ continue;
+
+ if (p->open (bus, scsi))
+ continue;
+
+ disk->id = grub_make_scsi_id (p->id, bus, lun);
+ disk->data = scsi;
+ scsi->dev = p;
+ scsi->lun = lun;
+ scsi->bus = bus;
+
+ grub_dprintf ("scsi", "dev opened\n");
+
+ err = grub_scsi_inquiry (scsi);
+ if (err)
+ {
+ grub_free (scsi);
+ grub_dprintf ("scsi", "inquiry failed\n");
+ return err;
+ }
+
+ grub_dprintf ("scsi", "inquiry: devtype=0x%02x removable=%d\n",
+ scsi->devtype, scsi->removable);
+
+ /* Try to be conservative about the device types
+ supported. */
+ if (scsi->devtype != grub_scsi_devtype_direct
+ && scsi->devtype != grub_scsi_devtype_cdrom)
+ {
+ grub_free (scsi);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "unknown SCSI device");
+ }
+
+ /* According to USB MS tests specification, issue Test Unit Ready
+ * until OK */
+ maxtime = grub_get_time_ms () + 5000; /* It is safer value */
+ do
+ {
+ /* Timeout is necessary - for example in case when we have
+ * universal card reader with more LUNs and we have only
+ * one card inserted (or none), so only one LUN (or none)
+ * will be ready - and we want not to hang... */
+ if (grub_get_time_ms () > maxtime)
+ {
+ err = GRUB_ERR_READ_ERROR;
+ grub_free (scsi);
+ grub_dprintf ("scsi", "LUN is not ready - timeout\n");
+ return err;
+ }
+ err = grub_scsi_test_unit_ready (scsi);
+ }
+ while (err == GRUB_ERR_READ_ERROR);
+ /* Reset grub_errno !
+ * It is set to some error code in loop before... */
+ grub_errno = GRUB_ERR_NONE;
+
+ /* Read capacity of media */
+ err = grub_scsi_read_capacity (scsi);
+ if (err)
+ {
+ grub_free (scsi);
+ grub_dprintf ("scsi", "READ CAPACITY failed\n");
+ return err;
+ }
+
+ /* SCSI blocks can be something else than 512, although GRUB
+ wants 512 byte blocks. */
+ disk->total_sectors = ((grub_uint64_t)scsi->size
+ * (grub_uint64_t)scsi->blocksize)
+ >> GRUB_DISK_SECTOR_BITS;
+
+ grub_dprintf ("scsi", "blocks=%u, blocksize=%u\n",
+ scsi->size, scsi->blocksize);
+ grub_dprintf ("scsi", "Disk total 512 sectors = %llu\n",
+ (unsigned long long) disk->total_sectors);
+
+ return GRUB_ERR_NONE;
+ }
+
+ grub_free (scsi);
+
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a SCSI disk");
+}
+
+static void
+grub_scsi_close (grub_disk_t disk)
+{
+ grub_scsi_t scsi;
+
+ scsi = disk->data;
+ if (scsi->dev->close)
+ scsi->dev->close (scsi);
+ grub_free (scsi);
+}
+
+static grub_err_t
+grub_scsi_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+
+ scsi = disk->data;
+
+ /* SCSI sectors are variable in size. GRUB uses 512 byte
+ sectors. */
+ if (scsi->blocksize != GRUB_DISK_SECTOR_SIZE)
+ {
+ unsigned spb = scsi->blocksize >> GRUB_DISK_SECTOR_BITS;
+ if (spb == 0 || (scsi->blocksize & (GRUB_DISK_SECTOR_SIZE - 1)) != 0)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported SCSI block size");
+
+ grub_uint32_t sector_mod = 0;
+ sector = grub_divmod64 (sector, spb, &sector_mod);
+
+ if (! (sector_mod == 0 && size % spb == 0))
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unaligned SCSI read not supported");
+
+ size /= spb;
+ }
+
+ /* Depending on the type, select a read function. */
+ switch (scsi->devtype)
+ {
+ case grub_scsi_devtype_direct:
+ return grub_scsi_read10 (disk, sector, size, buf);
+
+ case grub_scsi_devtype_cdrom:
+ return grub_scsi_read12 (disk, sector, size, buf);
+ }
+
+ /* XXX: Never reached. */
+ return GRUB_ERR_NONE;
+
+#if 0 /* Workaround - it works - but very slowly, from some reason
+ * unknown to me (specially on OHCI). Do not use it. */
+ /* Split transfer requests to device sector size because */
+ /* some devices are not able to transfer more than 512-1024 bytes */
+ grub_err_t err = GRUB_ERR_NONE;
+
+ for ( ; size; size--)
+ {
+ /* Depending on the type, select a read function. */
+ switch (scsi->devtype)
+ {
+ case grub_scsi_devtype_direct:
+ err = grub_scsi_read10 (disk, sector, 1, buf);
+ break;
+
+ case grub_scsi_devtype_cdrom:
+ err = grub_scsi_read12 (disk, sector, 1, buf);
+ break;
+
+ default: /* This should not happen */
+ return GRUB_ERR_READ_ERROR;
+ }
+ if (err)
+ return err;
+ sector++;
+ buf += scsi->blocksize;
+ }
+
+ return err;
+#endif
+}
+
+static grub_err_t
+grub_scsi_write (grub_disk_t disk __attribute((unused)),
+ grub_disk_addr_t sector __attribute((unused)),
+ grub_size_t size __attribute((unused)),
+ const char *buf __attribute((unused)))
+{
+#if 0
+ /* XXX: Not tested yet! */
+
+ /* XXX: This should depend on the device type? */
+ return grub_scsi_write10 (disk, sector, size, buf);
+#endif
+ return GRUB_ERR_NOT_IMPLEMENTED_YET;
+}
+
+
+static struct grub_disk_dev grub_scsi_dev =
+ {
+ .name = "scsi",
+ .id = GRUB_DISK_DEVICE_SCSI_ID,
+ .iterate = grub_scsi_iterate,
+ .open = grub_scsi_open,
+ .close = grub_scsi_close,
+ .read = grub_scsi_read,
+ .write = grub_scsi_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(scsi)
+{
+ grub_disk_dev_register (&grub_scsi_dev);
+}
+
+GRUB_MOD_FINI(scsi)
+{
+ grub_disk_dev_unregister (&grub_scsi_dev);
+}
diff --git a/grub-core/disk/usbms.c b/grub-core/disk/usbms.c
new file mode 100644
index 0000000..2f1dbd4
--- /dev/null
+++ b/grub-core/disk/usbms.c
@@ -0,0 +1,441 @@
+/* usbms.c - USB Mass Storage Support. */
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/scsi.h>
+#include <grub/scsicmd.h>
+#include <grub/misc.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_USBMS_DIRECTION_BIT 7
+
+/* The USB Mass Storage Command Block Wrapper. */
+struct grub_usbms_cbw
+{
+ grub_uint32_t signature;
+ grub_uint32_t tag;
+ grub_uint32_t transfer_length;
+ grub_uint8_t flags;
+ grub_uint8_t lun;
+ grub_uint8_t length;
+ grub_uint8_t cbwcb[16];
+} __attribute__ ((packed));
+
+struct grub_usbms_csw
+{
+ grub_uint32_t signature;
+ grub_uint32_t tag;
+ grub_uint32_t residue;
+ grub_uint8_t status;
+} __attribute__ ((packed));
+
+struct grub_usbms_dev
+{
+ struct grub_usb_device *dev;
+
+ int luns;
+
+ int config;
+ int interface;
+ struct grub_usb_desc_endp *in;
+ struct grub_usb_desc_endp *out;
+
+ int in_maxsz;
+ int out_maxsz;
+};
+typedef struct grub_usbms_dev *grub_usbms_dev_t;
+
+/* FIXME: remove limit. */
+#define MAX_USBMS_DEVICES 128
+static grub_usbms_dev_t grub_usbms_devices[MAX_USBMS_DEVICES];
+static int first_available_slot = 0;
+
+static grub_err_t
+grub_usbms_reset (grub_usb_device_t dev, int interface)
+{
+ grub_usb_err_t u;
+ u = grub_usb_control_msg (dev, 0x21, 255, 0, interface, 0, 0);
+ if (u)
+ return grub_error (GRUB_ERR_IO, "USB error %d", u);
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_usbms_detach (grub_usb_device_t usbdev, int config, int interface)
+{
+ unsigned i;
+ for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
+ if (grub_usbms_devices[i] && grub_usbms_devices[i]->dev == usbdev
+ && grub_usbms_devices[i]->interface == interface
+ && grub_usbms_devices[i]->config == config)
+ {
+ grub_free (grub_usbms_devices[i]);
+ grub_usbms_devices[i] = 0;
+ }
+}
+
+static int
+grub_usbms_attach (grub_usb_device_t usbdev, int configno, int interfno)
+{
+ struct grub_usb_desc_if *interf
+ = usbdev->config[configno].interf[interfno].descif;
+ int j;
+ grub_uint8_t luns = 0;
+ unsigned curnum;
+ grub_usb_err_t err;
+
+ if (first_available_slot == ARRAY_SIZE (grub_usbms_devices))
+ return 0;
+
+ curnum = first_available_slot;
+ first_available_slot++;
+
+ interf = usbdev->config[configno].interf[interfno].descif;
+
+ if ((interf->subclass != GRUB_USBMS_SUBCLASS_BULK
+ /* Experimental support of RBC, MMC-2, UFI, SFF-8070i devices */
+ && interf->subclass != GRUB_USBMS_SUBCLASS_RBC
+ && interf->subclass != GRUB_USBMS_SUBCLASS_MMC2
+ && interf->subclass != GRUB_USBMS_SUBCLASS_UFI
+ && interf->subclass != GRUB_USBMS_SUBCLASS_SFF8070 )
+ || interf->protocol != GRUB_USBMS_PROTOCOL_BULK)
+ return 0;
+
+ grub_usbms_devices[curnum] = grub_zalloc (sizeof (struct grub_usbms_dev));
+ if (! grub_usbms_devices[curnum])
+ return 0;
+
+ grub_usbms_devices[curnum]->dev = usbdev;
+ grub_usbms_devices[curnum]->interface = interfno;
+
+ grub_dprintf ("usbms", "alive\n");
+
+ /* Iterate over all endpoints of this interface, at least a
+ IN and OUT bulk endpoint are required. */
+ for (j = 0; j < interf->endpointcnt; j++)
+ {
+ struct grub_usb_desc_endp *endp;
+ endp = &usbdev->config[0].interf[interfno].descendp[j];
+
+ if ((endp->endp_addr & 128) && (endp->attrib & 3) == 2)
+ {
+ /* Bulk IN endpoint. */
+ grub_usbms_devices[curnum]->in = endp;
+ /* Clear Halt is not possible yet! */
+ /* grub_usb_clear_halt (usbdev, endp->endp_addr); */
+ grub_usbms_devices[curnum]->in_maxsz = endp->maxpacket;
+ }
+ else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2)
+ {
+ /* Bulk OUT endpoint. */
+ grub_usbms_devices[curnum]->out = endp;
+ /* Clear Halt is not possible yet! */
+ /* grub_usb_clear_halt (usbdev, endp->endp_addr); */
+ grub_usbms_devices[curnum]->out_maxsz = endp->maxpacket;
+ }
+ }
+
+ if (!grub_usbms_devices[curnum]->in || !grub_usbms_devices[curnum]->out)
+ {
+ grub_free (grub_usbms_devices[curnum]);
+ grub_usbms_devices[curnum] = 0;
+ return 0;
+ }
+
+ grub_dprintf ("usbms", "alive\n");
+
+ /* XXX: Activate the first configuration. */
+ grub_usb_set_configuration (usbdev, 1);
+
+ /* Query the amount of LUNs. */
+ err = grub_usb_control_msg (usbdev, 0xA1, 254, 0, interfno, 1, (char *) &luns);
+
+ if (err)
+ {
+ /* In case of a stall, clear the stall. */
+ if (err == GRUB_USB_ERR_STALL)
+ {
+ grub_usb_clear_halt (usbdev, grub_usbms_devices[curnum]->in->endp_addr);
+ grub_usb_clear_halt (usbdev, grub_usbms_devices[curnum]->out->endp_addr);
+ }
+ /* Just set the amount of LUNs to one. */
+ grub_errno = GRUB_ERR_NONE;
+ grub_usbms_devices[curnum]->luns = 1;
+ }
+ else
+ /* luns = 0 means one LUN with ID 0 present ! */
+ /* We get from device not number of LUNs but highest
+ * LUN number. LUNs are numbered from 0,
+ * i.e. number of LUNs is luns+1 ! */
+ grub_usbms_devices[curnum]->luns = luns + 1;
+
+ grub_dprintf ("usbms", "alive\n");
+
+ usbdev->config[configno].interf[interfno].detach_hook = grub_usbms_detach;
+
+#if 0 /* All this part should be probably deleted.
+ * This make trouble on some devices if they are not in
+ * Phase Error state - and there they should be not in such state...
+ * Bulk only mass storage reset procedure should be used only
+ * on place and in time when it is really necessary. */
+ /* Reset recovery procedure */
+ /* Bulk-Only Mass Storage Reset, after the reset commands
+ will be accepted. */
+ grub_usbms_reset (usbdev, i);
+ grub_usb_clear_halt (usbdev, usbms->in->endp_addr);
+ grub_usb_clear_halt (usbdev, usbms->out->endp_addr);
+#endif
+
+ return 1;
+}
+
+
+
+static int
+grub_usbms_iterate (int (*hook) (int bus, int luns))
+{
+ unsigned i;
+
+ grub_usb_poll_devices ();
+
+ for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
+ if (grub_usbms_devices[i])
+ {
+ if (hook (i, grub_usbms_devices[i]->luns))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf, int read_write)
+{
+ struct grub_usbms_cbw cbw;
+ grub_usbms_dev_t dev = (grub_usbms_dev_t) scsi->data;
+ struct grub_usbms_csw status;
+ static grub_uint32_t tag = 0;
+ grub_usb_err_t err = GRUB_USB_ERR_NONE;
+ grub_usb_err_t errCSW = GRUB_USB_ERR_NONE;
+ int retrycnt = 3 + 1;
+
+ retry:
+ retrycnt--;
+ if (retrycnt == 0)
+ return grub_error (GRUB_ERR_IO, "USB Mass Storage stalled");
+
+ /* Setup the request. */
+ grub_memset (&cbw, 0, sizeof (cbw));
+ cbw.signature = grub_cpu_to_le32 (0x43425355);
+ cbw.tag = tag++;
+ cbw.transfer_length = grub_cpu_to_le32 (size);
+ cbw.flags = (!read_write) << GRUB_USBMS_DIRECTION_BIT;
+ cbw.lun = scsi->lun; /* In USB MS CBW are LUN bits on another place than in SCSI CDB, both should be set correctly. */
+ cbw.length = cmdsize;
+ grub_memcpy (cbw.cbwcb, cmd, cmdsize);
+
+ /* Debug print of CBW content. */
+ grub_dprintf ("usb", "CBW: sign=0x%08x tag=0x%08x len=0x%08x\n",
+ cbw.signature, cbw.tag, cbw.transfer_length);
+ grub_dprintf ("usb", "CBW: flags=0x%02x lun=0x%02x CB_len=0x%02x\n",
+ cbw.flags, cbw.lun, cbw.length);
+ grub_dprintf ("usb", "CBW: cmd:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ cbw.cbwcb[ 0], cbw.cbwcb[ 1], cbw.cbwcb[ 2], cbw.cbwcb[ 3],
+ cbw.cbwcb[ 4], cbw.cbwcb[ 5], cbw.cbwcb[ 6], cbw.cbwcb[ 7],
+ cbw.cbwcb[ 8], cbw.cbwcb[ 9], cbw.cbwcb[10], cbw.cbwcb[11],
+ cbw.cbwcb[12], cbw.cbwcb[13], cbw.cbwcb[14], cbw.cbwcb[15]);
+
+ /* Write the request.
+ * XXX: Error recovery is maybe still not fully correct. */
+ err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr,
+ sizeof (cbw), (char *) &cbw);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ {
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ goto CheckCSW;
+ }
+ return grub_error (GRUB_ERR_IO, "USB Mass Storage request failed");
+ }
+
+ /* Read/write the data, (maybe) according to specification. */
+ if (size && (read_write == 0))
+ {
+ err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, size, buf);
+ grub_dprintf ("usb", "read: %d %d\n", err, GRUB_USB_ERR_STALL);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ goto CheckCSW;
+ }
+ /* Debug print of received data. */
+ grub_dprintf ("usb", "buf:\n");
+ if (size <= 64)
+ {
+ unsigned i;
+ for (i = 0; i < size; i++)
+ grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]);
+ }
+ else
+ grub_dprintf ("usb", "Too much data for debug print...\n");
+ }
+ else if (size)
+ {
+ err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr, size, buf);
+ grub_dprintf ("usb", "write: %d %d\n", err, GRUB_USB_ERR_STALL);
+ grub_dprintf ("usb", "First 16 bytes of sent data:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ buf[ 0], buf[ 1], buf[ 2], buf[ 3],
+ buf[ 4], buf[ 5], buf[ 6], buf[ 7],
+ buf[ 8], buf[ 9], buf[10], buf[11],
+ buf[12], buf[13], buf[14], buf[15]);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ goto CheckCSW;
+ }
+ /* Debug print of sent data. */
+ if (size <= 256)
+ {
+ unsigned i;
+ for (i=0; i<size; i++)
+ grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]);
+ }
+ else
+ grub_dprintf ("usb", "Too much data for debug print...\n");
+ }
+
+ /* Read the status - (maybe) according to specification. */
+CheckCSW:
+ errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
+ sizeof (status), (char *) &status);
+ if (errCSW)
+ {
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
+ sizeof (status), (char *) &status);
+ if (errCSW)
+ { /* Bulk-only reset device. */
+ grub_dprintf ("usb", "Bulk-only reset device - errCSW\n");
+ grub_usbms_reset (dev->dev, dev->interface);
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ goto retry;
+ }
+ }
+
+ /* Debug print of CSW content. */
+ grub_dprintf ("usb", "CSW: sign=0x%08x tag=0x%08x resid=0x%08x\n",
+ status.signature, status.tag, status.residue);
+ grub_dprintf ("usb", "CSW: status=0x%02x\n", status.status);
+
+ /* If phase error or not valid signature, do bulk-only reset device. */
+ if ((status.status == 2) ||
+ (status.signature != grub_cpu_to_le32(0x53425355)))
+ { /* Bulk-only reset device. */
+ grub_dprintf ("usb", "Bulk-only reset device - bad status\n");
+ grub_usbms_reset (dev->dev, dev->interface);
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+
+ goto retry;
+ }
+
+ /* If "command failed" status or data transfer failed -> error */
+ if ((status.status || err) && !read_write)
+ return grub_error (GRUB_ERR_READ_ERROR,
+ "error communication with USB Mass Storage device");
+ else if ((status.status || err) && read_write)
+ return grub_error (GRUB_ERR_WRITE_ERROR,
+ "error communication with USB Mass Storage device");
+
+ return GRUB_ERR_NONE;
+}
+
+
+static grub_err_t
+grub_usbms_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf)
+{
+ return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 0);
+}
+
+static grub_err_t
+grub_usbms_write (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf)
+{
+ return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 1);
+}
+
+static grub_err_t
+grub_usbms_open (int devnum, struct grub_scsi *scsi)
+{
+ grub_usb_poll_devices ();
+
+ if (!grub_usbms_devices[devnum])
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "unknown USB Mass Storage device");
+
+ scsi->data = grub_usbms_devices[devnum];
+ scsi->luns = grub_usbms_devices[devnum]->luns;
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_scsi_dev grub_usbms_dev =
+ {
+ .name = "usb",
+ .id = GRUB_SCSI_SUBSYSTEM_USBMS,
+ .iterate = grub_usbms_iterate,
+ .open = grub_usbms_open,
+ .read = grub_usbms_read,
+ .write = grub_usbms_write
+ };
+
+static struct grub_usb_attach_desc attach_hook =
+{
+ .class = GRUB_USB_CLASS_MASS_STORAGE,
+ .hook = grub_usbms_attach
+};
+
+GRUB_MOD_INIT(usbms)
+{
+ grub_usb_register_attach_hook_class (&attach_hook);
+ grub_scsi_dev_register (&grub_usbms_dev);
+}
+
+GRUB_MOD_FINI(usbms)
+{
+ unsigned i;
+ for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
+ {
+ grub_usbms_devices[i]->dev->config[grub_usbms_devices[i]->config]
+ .interf[grub_usbms_devices[i]->interface].detach_hook = 0;
+ grub_usbms_devices[i]->dev->config[grub_usbms_devices[i]->config]
+ .interf[grub_usbms_devices[i]->interface].attached = 0;
+ }
+ grub_usb_unregister_attach_hook_class (&attach_hook);
+ grub_scsi_dev_unregister (&grub_usbms_dev);
+}