From 041d1ea37802bf7178a31a53f96c26efa6b8fb7b Mon Sep 17 00:00:00 2001 From: James Date: Fri, 16 Nov 2012 10:41:01 +0000 Subject: fish --- grub-core/disk/ata.c | 933 ++++++++++++++++++++++++++++++++++++++ grub-core/disk/ata_pthru.c | 108 +++++ grub-core/disk/dmraid_nvidia.c | 171 +++++++ grub-core/disk/efi/efidisk.c | 850 ++++++++++++++++++++++++++++++++++ grub-core/disk/host.c | 95 ++++ grub-core/disk/i386/pc/biosdisk.c | 641 ++++++++++++++++++++++++++ grub-core/disk/ieee1275/nand.c | 216 +++++++++ grub-core/disk/ieee1275/ofdisk.c | 373 +++++++++++++++ grub-core/disk/loopback.c | 232 ++++++++++ grub-core/disk/lvm.c | 896 ++++++++++++++++++++++++++++++++++++ grub-core/disk/mdraid1x_linux.c | 247 ++++++++++ grub-core/disk/mdraid_linux.c | 248 ++++++++++ grub-core/disk/memdisk.c | 117 +++++ grub-core/disk/raid.c | 812 +++++++++++++++++++++++++++++++++ grub-core/disk/raid5_recover.c | 76 ++++ grub-core/disk/raid6_recover.c | 230 ++++++++++ grub-core/disk/scsi.c | 606 +++++++++++++++++++++++++ grub-core/disk/usbms.c | 441 ++++++++++++++++++ 18 files changed, 7292 insertions(+) create mode 100644 grub-core/disk/ata.c create mode 100644 grub-core/disk/ata_pthru.c create mode 100644 grub-core/disk/dmraid_nvidia.c create mode 100644 grub-core/disk/efi/efidisk.c create mode 100644 grub-core/disk/host.c create mode 100644 grub-core/disk/i386/pc/biosdisk.c create mode 100644 grub-core/disk/ieee1275/nand.c create mode 100644 grub-core/disk/ieee1275/ofdisk.c create mode 100644 grub-core/disk/loopback.c create mode 100644 grub-core/disk/lvm.c create mode 100644 grub-core/disk/mdraid1x_linux.c create mode 100644 grub-core/disk/mdraid_linux.c create mode 100644 grub-core/disk/memdisk.c create mode 100644 grub-core/disk/raid.c create mode 100644 grub-core/disk/raid5_recover.c create mode 100644 grub-core/disk/raid6_recover.c create mode 100644 grub-core/disk/scsi.c create mode 100644 grub-core/disk/usbms.c (limited to 'grub-core/disk') 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +/* When using the disk, make a reference to this module. Otherwise + the user will end up with a useless module :-). */ + +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, ®s); + + 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, ®s); + 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, ®s); + 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, ®s); + /* 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, ®s); + } + 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, ®s); + + 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, ®s); + + /* 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, ®s); + + /* 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 . + */ + +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include + +#ifdef GRUB_UTIL +#include +#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 . + */ + +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include + +/* 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 . + */ + +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#ifdef GRUB_UTIL +#include +#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 . + */ + +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, §or_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 . + */ + +#include +#include +#include +#include +#include +#include + +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; idev, 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); +} -- cgit v1.2.3