MErge mainline into intwrap

This commit is contained in:
Vladimir 'phcoder' Serbinenko 2010-08-25 23:39:42 +02:00
commit afba9f98ec
719 changed files with 55744 additions and 25714 deletions

917
grub-core/disk/ata.c Normal file
View file

@ -0,0 +1,917 @@
/* ata.c - ATA disk access. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/ata.h>
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/time.h>
#include <grub/pci.h>
#include <grub/scsi.h>
#include <grub/cs5536.h>
/* 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_check_ready (dev))
{
grub_free (info);
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, GRUB_ATA_TOUT_STD))
{
grub_free (info);
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_check_ready (dev))
{
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, 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)
/* No device, return error but don't print message. */
return GRUB_ERR_UNKNOWN_DEVICE;
else
/* 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->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->has_partitions = 1;
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);
}

107
grub-core/disk/ata_pthru.c Normal file
View file

@ -0,0 +1,107 @@
/* ata_pthru.c - ATA pass through for ata.mod. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/ata.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/mm.h>
/* 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;
}

View file

@ -0,0 +1,169 @@
/* dmraid_nvidia.c - module to handle Nvidia fakeraid. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
#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);
}

View file

@ -0,0 +1,851 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/disk.h>
#include <grub/partition.h>
#include <grub/mm.h>
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/err.h>
#include <grub/term.h>
#include <grub/efi/api.h>
#include <grub/efi/efi.h>
#include <grub/efi/disk.h>
struct grub_efidisk_data
{
grub_efi_handle_t handle;
grub_efi_device_path_t *device_path;
grub_efi_device_path_t *last_device_path;
grub_efi_block_io_t *block_io;
grub_efi_disk_io_t *disk_io;
struct grub_efidisk_data *next;
};
/* GUIDs. */
static grub_efi_guid_t disk_io_guid = GRUB_EFI_DISK_IO_GUID;
static grub_efi_guid_t block_io_guid = GRUB_EFI_BLOCK_IO_GUID;
static struct grub_efidisk_data *fd_devices;
static struct grub_efidisk_data *hd_devices;
static struct grub_efidisk_data *cd_devices;
/* Duplicate a device path. */
static grub_efi_device_path_t *
duplicate_device_path (const grub_efi_device_path_t *dp)
{
grub_efi_device_path_t *p;
grub_size_t total_size = 0;
for (p = (grub_efi_device_path_t *) dp;
;
p = GRUB_EFI_NEXT_DEVICE_PATH (p))
{
total_size += GRUB_EFI_DEVICE_PATH_LENGTH (p);
if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (p))
break;
}
p = grub_malloc (total_size);
if (! p)
return 0;
grub_memcpy (p, dp, total_size);
return p;
}
/* Return the device path node right before the end node. */
static grub_efi_device_path_t *
find_last_device_path (const grub_efi_device_path_t *dp)
{
grub_efi_device_path_t *next, *p;
if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp))
return 0;
for (p = (grub_efi_device_path_t *) dp, next = GRUB_EFI_NEXT_DEVICE_PATH (p);
! GRUB_EFI_END_ENTIRE_DEVICE_PATH (next);
p = next, next = GRUB_EFI_NEXT_DEVICE_PATH (next))
;
return p;
}
/* Compare device paths. */
static int
compare_device_paths (const grub_efi_device_path_t *dp1,
const grub_efi_device_path_t *dp2)
{
if (! dp1 || ! dp2)
/* Return non-zero. */
return 1;
while (1)
{
grub_efi_uint8_t type1, type2;
grub_efi_uint8_t subtype1, subtype2;
grub_efi_uint16_t len1, len2;
int ret;
type1 = GRUB_EFI_DEVICE_PATH_TYPE (dp1);
type2 = GRUB_EFI_DEVICE_PATH_TYPE (dp2);
if (type1 != type2)
return (int) type2 - (int) type1;
subtype1 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp1);
subtype2 = GRUB_EFI_DEVICE_PATH_SUBTYPE (dp2);
if (subtype1 != subtype2)
return (int) subtype1 - (int) subtype2;
len1 = GRUB_EFI_DEVICE_PATH_LENGTH (dp1);
len2 = GRUB_EFI_DEVICE_PATH_LENGTH (dp2);
if (len1 != len2)
return (int) len1 - (int) len2;
ret = grub_memcmp (dp1, dp2, len1);
if (ret != 0)
return ret;
if (GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp1))
break;
dp1 = (grub_efi_device_path_t *) ((char *) dp1 + len1);
dp2 = (grub_efi_device_path_t *) ((char *) dp2 + len2);
}
return 0;
}
static struct grub_efidisk_data *
make_devices (void)
{
grub_efi_uintn_t num_handles;
grub_efi_handle_t *handles;
grub_efi_handle_t *handle;
struct grub_efidisk_data *devices = 0;
/* Find handles which support the disk io interface. */
handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &disk_io_guid,
0, &num_handles);
if (! handles)
return 0;
/* Make a linked list of devices. */
for (handle = handles; num_handles--; handle++)
{
grub_efi_device_path_t *dp;
grub_efi_device_path_t *ldp;
struct grub_efidisk_data *d;
grub_efi_block_io_t *bio;
grub_efi_disk_io_t *dio;
dp = grub_efi_get_device_path (*handle);
if (! dp)
continue;
ldp = find_last_device_path (dp);
if (! ldp)
/* This is empty. Why? */
continue;
bio = grub_efi_open_protocol (*handle, &block_io_guid,
GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
dio = grub_efi_open_protocol (*handle, &disk_io_guid,
GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (! bio || ! dio)
/* This should not happen... Why? */
continue;
d = grub_malloc (sizeof (*d));
if (! d)
{
/* Uggh. */
grub_free (handles);
return 0;
}
d->handle = *handle;
d->device_path = dp;
d->last_device_path = ldp;
d->block_io = bio;
d->disk_io = dio;
d->next = devices;
devices = d;
}
grub_free (handles);
return devices;
}
/* Find the parent device. */
static struct grub_efidisk_data *
find_parent_device (struct grub_efidisk_data *devices,
struct grub_efidisk_data *d)
{
grub_efi_device_path_t *dp, *ldp;
struct grub_efidisk_data *parent;
dp = duplicate_device_path (d->device_path);
if (! dp)
return 0;
ldp = find_last_device_path (dp);
ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
ldp->length[0] = sizeof (*ldp);
ldp->length[1] = 0;
for (parent = devices; parent; parent = parent->next)
{
/* Ignore itself. */
if (parent == d)
continue;
if (compare_device_paths (parent->device_path, dp) == 0)
{
/* Found. */
if (! parent->last_device_path)
parent = 0;
break;
}
}
grub_free (dp);
return parent;
}
static int
iterate_child_devices (struct grub_efidisk_data *devices,
struct grub_efidisk_data *d,
int (*hook) (struct grub_efidisk_data *child))
{
struct grub_efidisk_data *p;
for (p = devices; p; p = p->next)
{
grub_efi_device_path_t *dp, *ldp;
dp = duplicate_device_path (p->device_path);
if (! dp)
return 0;
ldp = find_last_device_path (dp);
ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
ldp->length[0] = sizeof (*ldp);
ldp->length[1] = 0;
if (compare_device_paths (dp, d->device_path) == 0)
if (hook (p))
{
grub_free (dp);
return 1;
}
grub_free (dp);
}
return 0;
}
/* Add a device into a list of devices in an ascending order. */
static void
add_device (struct grub_efidisk_data **devices, struct grub_efidisk_data *d)
{
struct grub_efidisk_data **p;
struct grub_efidisk_data *n;
for (p = devices; *p; p = &((*p)->next))
{
int ret;
ret = compare_device_paths (find_last_device_path ((*p)->device_path),
find_last_device_path (d->device_path));
if (ret == 0)
ret = compare_device_paths ((*p)->device_path,
d->device_path);
if (ret == 0)
return;
else if (ret > 0)
break;
}
n = grub_malloc (sizeof (*n));
if (! n)
return;
grub_memcpy (n, d, sizeof (*n));
n->next = (*p);
(*p) = n;
}
/* Name the devices. */
static void
name_devices (struct grub_efidisk_data *devices)
{
struct grub_efidisk_data *d;
/* First, identify devices by media device paths. */
for (d = devices; d; d = d->next)
{
grub_efi_device_path_t *dp;
dp = d->last_device_path;
if (! dp)
continue;
if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE)
{
int is_hard_drive = 0;
switch (GRUB_EFI_DEVICE_PATH_SUBTYPE (dp))
{
case GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE:
is_hard_drive = 1;
/* Fall through by intention. */
case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE:
{
struct grub_efidisk_data *parent;
parent = find_parent_device (devices, d);
if (parent)
{
if (is_hard_drive)
{
#if 0
grub_printf ("adding a hard drive by a partition: ");
grub_print_device_path (parent->device_path);
#endif
add_device (&hd_devices, parent);
}
else
{
#if 0
grub_printf ("adding a cdrom by a partition: ");
grub_print_device_path (parent->device_path);
#endif
add_device (&cd_devices, parent);
}
/* Mark the parent as used. */
parent->last_device_path = 0;
}
}
/* Mark itself as used. */
d->last_device_path = 0;
break;
default:
/* For now, ignore the others. */
break;
}
}
}
/* Let's see what can be added more. */
for (d = devices; d; d = d->next)
{
grub_efi_device_path_t *dp;
grub_efi_block_io_media_t *m;
dp = d->last_device_path;
if (! dp)
continue;
m = d->block_io->media;
if (m->logical_partition)
{
/* Only one partition in a non-media device. Assume that this
is a floppy drive. */
#if 0
grub_printf ("adding a floppy by guessing: ");
grub_print_device_path (d->device_path);
#endif
add_device (&fd_devices, d);
}
else if (m->read_only && m->block_size > GRUB_DISK_SECTOR_SIZE)
{
/* This check is too heuristic, but assume that this is a
CDROM drive. */
#if 0
grub_printf ("adding a cdrom by guessing: ");
grub_print_device_path (d->device_path);
#endif
add_device (&cd_devices, d);
}
else
{
/* The default is a hard drive. */
#if 0
grub_printf ("adding a hard drive by guessing: ");
grub_print_device_path (d->device_path);
#endif
add_device (&hd_devices, d);
}
}
}
static void
free_devices (struct grub_efidisk_data *devices)
{
struct grub_efidisk_data *p, *q;
for (p = devices; p; p = q)
{
q = p->next;
grub_free (p);
}
}
/* Enumerate all disks to name devices. */
static void
enumerate_disks (void)
{
struct grub_efidisk_data *devices;
devices = make_devices ();
if (! devices)
return;
name_devices (devices);
free_devices (devices);
}
static int
grub_efidisk_iterate (int (*hook) (const char *name))
{
struct grub_efidisk_data *d;
char buf[16];
int count;
for (d = fd_devices, count = 0; d; d = d->next, count++)
{
grub_snprintf (buf, sizeof (buf), "fd%d", count);
grub_dprintf ("efidisk", "iterating %s\n", buf);
if (hook (buf))
return 1;
}
for (d = hd_devices, count = 0; d; d = d->next, count++)
{
grub_snprintf (buf, sizeof (buf), "hd%d", count);
grub_dprintf ("efidisk", "iterating %s\n", buf);
if (hook (buf))
return 1;
}
for (d = cd_devices, count = 0; d; d = d->next, count++)
{
grub_snprintf (buf, sizeof (buf), "cd%d", count);
grub_dprintf ("efidisk", "iterating %s\n", buf);
if (hook (buf))
return 1;
}
return 0;
}
static int
get_drive_number (const char *name)
{
unsigned long drive;
if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd')
goto fail;
drive = grub_strtoul (name + 2, 0, 10);
if (grub_errno != GRUB_ERR_NONE)
goto fail;
return (int) drive ;
fail:
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk");
return -1;
}
static struct grub_efidisk_data *
get_device (struct grub_efidisk_data *devices, int num)
{
struct grub_efidisk_data *d;
for (d = devices; d && num; d = d->next, num--)
;
if (num == 0)
return d;
return 0;
}
static grub_err_t
grub_efidisk_open (const char *name, struct grub_disk *disk)
{
int num;
struct grub_efidisk_data *d = 0;
grub_efi_block_io_media_t *m;
grub_dprintf ("efidisk", "opening %s\n", name);
num = get_drive_number (name);
if (num < 0)
return grub_errno;
switch (name[0])
{
case 'f':
disk->has_partitions = 0;
d = get_device (fd_devices, num);
break;
case 'c':
/* FIXME: a CDROM should have partitions, but not implemented yet. */
disk->has_partitions = 0;
d = get_device (cd_devices, num);
break;
case 'h':
disk->has_partitions = 1;
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;
char *partition_name = 0;
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)
{
partition_name = grub_partition_get_name (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 (! partition_name)
{
grub_disk_close (parent);
return 0;
}
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;
}

96
grub-core/disk/host.c Normal file
View file

@ -0,0 +1,96 @@
/* host.c - Dummy disk driver to provide access to the hosts filesystem */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
/* When using the disk, make a reference to this module. Otherwise
the user will end up with a useless module :-). */
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/misc.h>
int grub_disk_host_i_want_a_reference;
static int
grub_host_iterate (int (*hook) (const char *name))
{
if (hook ("host"))
return 1;
return 0;
}
static grub_err_t
grub_host_open (const char *name, grub_disk_t disk)
{
if (grub_strcmp (name, "host"))
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a host disk");
disk->total_sectors = 0;
disk->id = (unsigned long) "host";
disk->has_partitions = 0;
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);
}

View file

@ -0,0 +1,629 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/machine/biosdisk.h>
#include <grub/machine/memory.h>
#include <grub/machine/kernel.h>
#include <grub/machine/int.h>
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/types.h>
#include <grub/misc.h>
#include <grub/err.h>
#include <grub/term.h>
static int cd_drive = 0;
static int grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap);
static int grub_biosdisk_get_num_floppies (void)
{
struct grub_bios_int_registers regs;
int drive;
/* reset the disk system first */
regs.eax = 0;
regs.edx = 0;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
for (drive = 0; drive < 2; drive++)
{
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT | GRUB_CPU_INT_FLAGS_CARRY;
regs.edx = drive;
/* call GET DISK TYPE */
regs.eax = 0x1500;
grub_bios_interrupt (0x13, &regs);
if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
break;
/* check if this drive exists */
if (!(regs.eax & 0x300))
break;
}
return drive;
}
/*
* Call IBM/MS INT13 Extensions (int 13 %ah=AH) for DRIVE. DAP
* is passed for disk address packet. If an error occurs, return
* non-zero, otherwise zero.
*/
static int
grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap)
{
struct grub_bios_int_registers regs;
regs.eax = ah << 8;
/* compute the address of disk_address_packet */
regs.ds = (((grub_addr_t) dap) & 0xffff0000) >> 4;
regs.esi = (((grub_addr_t) dap) & 0xffff);
regs.edx = drive;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
return (regs.eax >> 8) & 0xff;
}
/*
* Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write
* NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs,
* return non-zero, otherwise zero.
*/
static int
grub_biosdisk_rw_standard (int ah, int drive, int coff, int hoff,
int soff, int nsec, int segment)
{
int ret, i;
/* Try 3 times. */
for (i = 0; i < 3; i++)
{
struct grub_bios_int_registers regs;
/* set up CHS information */
/* set %ch to low eight bits of cylinder */
regs.ecx = (coff << 8) & 0xff00;
/* set bits 6-7 of %cl to high two bits of cylinder */
regs.ecx |= (coff >> 2) & 0xc0;
/* set bits 0-5 of %cl to sector */
regs.ecx |= soff & 0x3f;
/* set %dh to head and %dl to drive */
regs.edx = (drive & 0xff) | ((hoff << 8) & 0xff00);
/* set %ah to AH */
regs.eax = (ah << 8) & 0xff00;
/* set %al to NSEC */
regs.eax |= nsec & 0xff;
regs.ebx = 0;
regs.es = segment;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
/* check if successful */
if (!(regs.flags & GRUB_CPU_INT_FLAGS_CARRY))
return 0;
/* save return value */
ret = regs.eax >> 8;
/* if fail, reset the disk system */
regs.eax = 0;
regs.edx = (drive & 0xff);
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
}
return ret;
}
/*
* Check if LBA is supported for DRIVE. If it is supported, then return
* the major version of extensions, otherwise zero.
*/
static int
grub_biosdisk_check_int13_extensions (int drive)
{
struct grub_bios_int_registers regs;
regs.edx = drive & 0xff;
regs.eax = 0x4100;
regs.ebx = 0x55aa;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
return 0;
if ((regs.ebx & 0xffff) != 0xaa55)
return 0;
/* check if AH=0x42 is supported */
if (!(regs.ecx & 1))
return 0;
return (regs.eax >> 8) & 0xff;
}
/*
* Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an
* error occurs, then return non-zero, otherwise zero.
*/
static int
grub_biosdisk_get_diskinfo_standard (int drive,
unsigned long *cylinders,
unsigned long *heads,
unsigned long *sectors)
{
struct grub_bios_int_registers regs;
regs.eax = 0x0800;
regs.edx = drive & 0xff;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
/* Check if unsuccessful. Ignore return value if carry isn't set to
workaround some buggy BIOSes. */
if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
return (regs.eax & 0xff00) >> 8;
/* bogus BIOSes may not return an error number */
/* 0 sectors means no disk */
if (!(regs.ecx & 0x3f))
/* XXX 0x60 is one of the unused error numbers */
return 0x60;
/* the number of heads is counted from zero */
*heads = ((regs.edx >> 8) & 0xff) + 1;
*cylinders = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x0300)) + 1;
*sectors = regs.ecx & 0x3f;
return 0;
}
static int
grub_biosdisk_get_diskinfo_real (int drive, void *drp, grub_uint16_t ax)
{
struct grub_bios_int_registers regs;
regs.eax = ax;
/* compute the address of drive parameters */
regs.esi = ((grub_addr_t) drp) & 0xf;
regs.ds = ((grub_addr_t) drp) >> 4;
regs.edx = drive & 0xff;
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
grub_bios_interrupt (0x13, &regs);
/* Check if unsuccessful. Ignore return value if carry isn't set to
workaround some buggy BIOSes. */
if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
return (regs.eax & 0xff00) >> 8;
return 0;
}
/*
* Return the cdrom information of DRIVE in CDRP. If an error occurs,
* then return non-zero, otherwise zero.
*/
static int
grub_biosdisk_get_cdinfo_int13_extensions (int drive, void *cdrp)
{
return grub_biosdisk_get_diskinfo_real (drive, cdrp, 0x4b01);
}
/*
* Return the geometry of DRIVE in a drive parameters, DRP. If an error
* occurs, then return non-zero, otherwise zero.
*/
static int
grub_biosdisk_get_diskinfo_int13_extensions (int drive, void *drp)
{
return grub_biosdisk_get_diskinfo_real (drive, drp, 0x4800);
}
static int
grub_biosdisk_get_drive (const char *name)
{
unsigned long drive;
if ((name[0] != '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];
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->has_partitions = ((drive & 0x80) && (drive != cd_drive));
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 if (drive & 0x80)
{
/* 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;
grub_disk_dev_register (&grub_biosdisk_dev);
}
GRUB_MOD_FINI(biosdisk)
{
grub_disk_biosdisk_fini ();
}

View file

@ -0,0 +1,215 @@
/* nand.c - NAND flash disk access. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/dl.h>
#include <grub/ieee1275/ieee1275.h>
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->has_partitions = 0;
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);
}

View file

@ -0,0 +1,285 @@
/* ofdisk.c - Open Firmware disk access. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2004,2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/misc.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/ieee1275/ieee1275.h>
#include <grub/ieee1275/ofdisk.h>
struct ofdisk_hash_ent
{
char *devpath;
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 (char *devpath)
{
struct ofdisk_hash_ent **head = &ofdisk_hash[ofdisk_hash_fn(devpath)];
struct ofdisk_hash_ent *p = grub_malloc(sizeof (*p));
if (p)
{
p->devpath = devpath;
p->next = *head;
*head = p;
}
return p;
}
static int
grub_ofdisk_iterate (int (*hook) (const char *name))
{
auto int dev_iterate (struct grub_ieee1275_devalias *alias);
int dev_iterate (struct grub_ieee1275_devalias *alias)
{
int ret = 0;
grub_dprintf ("disk", "disk name = %s\n", alias->name);
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_OFDISK_SDCARD_ONLY))
{
grub_ieee1275_phandle_t dev;
char tmp[8];
if (grub_ieee1275_finddevice (alias->path, &dev))
{
grub_dprintf ("disk", "finddevice (%s) failed\n", alias->path);
return 0;
}
if (grub_ieee1275_get_property (dev, "iconname", tmp,
sizeof tmp, 0))
{
grub_dprintf ("disk", "get iconname failed\n");
return 0;
}
if (grub_strcmp (tmp, "sdmmc"))
{
grub_dprintf ("disk", "device is not an SD card\n");
return 0;
}
}
if (! grub_strcmp (alias->type, "block") &&
grub_strncmp (alias->name, "cdrom", 5))
ret = hook (alias->name);
return ret;
}
return grub_devalias_iterate (dev_iterate);
}
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;
}
if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PARTITION_0))
{
*p++ = ':';
*p++ = '0';
}
*p++ = '\0';
return devpath;
}
static grub_err_t
grub_ofdisk_open (const char *name, grub_disk_t disk)
{
grub_ieee1275_phandle_t dev;
grub_ieee1275_ihandle_t dev_ihandle = 0;
struct ofdisk_hash_ent *op;
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;
op = ofdisk_hash_find (devpath);
if (!op)
op = ofdisk_hash_add (devpath);
grub_free (devpath);
if (!op)
return grub_errno;
grub_dprintf ("disk", "Opening `%s'.\n", op->devpath);
if (grub_ieee1275_finddevice (op->devpath, &dev))
{
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read device properties");
goto fail;
}
if (grub_ieee1275_get_property (dev, "device_type", prop, sizeof (prop),
&actual))
{
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read the device type");
goto fail;
}
if (grub_strcmp (prop, "block"))
{
grub_error (GRUB_ERR_BAD_DEVICE, "not a block device");
goto fail;
}
grub_ieee1275_open (op->devpath, &dev_ihandle);
if (! dev_ihandle)
{
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
goto fail;
}
grub_dprintf ("disk", "Opened `%s' as handle %p.\n", op->devpath,
(void *) (unsigned long) dev_ihandle);
/* 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;
disk->id = (unsigned long) op;
/* XXX: Read this, somehow. */
disk->has_partitions = 1;
disk->data = (void *) (unsigned long) dev_ihandle;
return 0;
fail:
if (dev_ihandle)
grub_ieee1275_close (dev_ihandle);
return grub_errno;
}
static void
grub_ofdisk_close (grub_disk_t disk)
{
grub_dprintf ("disk", "Closing handle %p.\n",
(void *) disk->data);
grub_ieee1275_close ((grub_ieee1275_ihandle_t) (unsigned long) disk->data);
}
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;
pos = sector * 512UL;
grub_ieee1275_seek ((grub_ieee1275_ihandle_t) (unsigned long) disk->data,
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 ((grub_ieee1275_ihandle_t) (unsigned long) disk->data,
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)
{
grub_disk_dev_unregister (&grub_ofdisk_dev);
}

241
grub-core/disk/loopback.c Normal file
View file

@ -0,0 +1,241 @@
/* loopback.c - command to add loopback devices. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2005,2006,2007 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/file.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/extcmd.h>
#include <grub/i18n.h>
struct grub_loopback
{
char *devname;
grub_file_t file;
int has_partitions;
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},
{"partitions", 'p', 0, N_("Simulate a hard drive with partitions."), 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_t cmd, int argc, char **args)
{
struct grub_arg_list *state = state = cmd->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)
{
char *newname = grub_strdup (args[1]);
if (! newname)
goto fail;
grub_file_close (newdev->file);
newdev->file = file;
/* Set has_partitions when `--partitions' was used. */
newdev->has_partitions = state[1].set;
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;
/* Set has_partitions when `--partitions' was used. */
newdev->has_partitions = state[1].set;
/* 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. */
disk->total_sectors = ((dev->file->size + GRUB_DISK_SECTOR_SIZE - 1)
/ GRUB_DISK_SECTOR_SIZE);
disk->id = (unsigned long) dev;
disk->has_partitions = dev->has_partitions;
disk->data = dev->file;
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 = (grub_file_t) disk->data;
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,
GRUB_COMMAND_FLAG_BOTH,
N_("[-d|-p] 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);
}

693
grub-core/disk/lvm.c Normal file
View file

@ -0,0 +1,693 @@
/* lvm.c - module to read Logical Volumes. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/lvm.h>
#ifdef GRUB_UTIL
#include <grub/emu/misc.h>
#endif
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);
}
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] == '"');
}
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 (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->has_partitions = 0;
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
grub_lvm_read (grub_disk_t disk, grub_disk_addr_t sector,
grub_size_t size, char *buf)
{
grub_err_t err = 0;
struct grub_lvm_lv *lv = disk->data;
struct grub_lvm_vg *vg = lv->vg;
struct grub_lvm_segment *seg = lv->segments;
struct grub_lvm_pv *pv;
grub_uint64_t offset;
grub_uint64_t extent;
unsigned int i;
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 (seg->stripe_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_stripe *stripe = seg->stripes;
grub_uint64_t seg_offset; /* Offset of the segment in PV device. */
pv = stripe->pv;
seg_offset = ((grub_uint64_t) stripe->start
* (grub_uint64_t) vg->extent_size) + pv->start;
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_stripe *stripe = seg->stripes;
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->stripe_count, &stripenr);
a = grub_divmod64 (offset, seg->stripe_size * seg->stripe_count, NULL);
grub_divmod64 (offset, seg->stripe_size, &b);
offset = a * seg->stripe_size + b;
stripe += stripenr;
pv = stripe->pv;
seg_offset = ((grub_uint64_t) stripe->start
* (grub_uint64_t) vg->extent_size) + pv->start;
offset += seg_offset;
}
/* Check whether we actually know the physical volume we want to
read from. */
if (pv->disk)
err = grub_disk_read (pv->disk, offset, 0,
size << GRUB_DISK_SECTOR_BITS, buf);
else
err = grub_error (GRUB_ERR_UNKNOWN_DEVICE,
"physical volume %s not found", pv->name);
return err;
}
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 da_offset, da_size, 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;
disk = grub_disk_open (name);
if (!disk)
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)
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;
da_offset = grub_le_to_cpu64 (dlocn->offset);
da_size = grub_le_to_cpu64 (dlocn->size);
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");
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");
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)
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)
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)
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)
goto pvs_fail;
p = grub_strchr (p, '}');
if (p == NULL)
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;
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;
if (!grub_lvm_check_flag (p, "status", "VISIBLE"))
{
skip_lv = 1;
goto lv_parsed;
}
lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
if (p == NULL)
goto lvs_fail;
lv->segments = grub_malloc (sizeof (*seg) * lv->segment_count);
seg = lv->segments;
for (i = 0; i < lv->segment_count; i++)
{
struct grub_lvm_stripe *stripe;
p = grub_strstr (p, "segment");
if (p == NULL)
goto lvs_segment_fail;
seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
if (p == NULL)
goto lvs_segment_fail;
seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
if (p == NULL)
goto lvs_segment_fail;
if (grub_lvm_checkvalue (&p, "type = ", "snapshot"))
{
/* Found a snapshot, give up and move on. */
skip_lv = 1;
break;
}
seg->stripe_count = grub_lvm_getvalue (&p, "stripe_count = ");
if (p == NULL)
goto lvs_segment_fail;
lv->size += seg->extent_count * vg->extent_size;
if (seg->stripe_count != 1)
seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
seg->stripes = grub_malloc (sizeof (*stripe)
* seg->stripe_count);
stripe = seg->stripes;
p = grub_strstr (p, "stripes = [");
if (p == NULL)
goto lvs_segment_fail2;
p += sizeof("stripes = [") - 1;
for (j = 0; j < seg->stripe_count; j++)
{
char *pvname;
p = grub_strchr (p, '"');
if (p == NULL)
continue;
q = ++p;
while (*q != '"')
q++;
s = q - p;
pvname = grub_malloc (s + 1);
if (pvname == NULL)
goto lvs_segment_fail2;
grub_memcpy (pvname, p, s);
pvname[s] = '\0';
if (vg->pvs)
for (pv = vg->pvs; pv; pv = pv->next)
{
if (! grub_strcmp (pvname, pv->name))
{
stripe->pv = pv;
break;
}
}
grub_free(pvname);
stripe->start = grub_lvm_getvalue (&p, ",");
if (p == NULL)
continue;
stripe++;
}
seg++;
continue;
lvs_segment_fail2:
grub_free (seg->stripes);
lvs_segment_fail:
goto fail4;
}
lv_parsed:
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;
}
}
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);
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);
/* FIXME: free the lvm list. */
}

View file

@ -0,0 +1,424 @@
/* mdraid_linux.c - module to handle Linux Software RAID. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008,2009,2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
/* Linux RAID on disk structures and constants,
copied from include/linux/raid/md_p.h. */
#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));
/*
* 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_09 (grub_disk_addr_t sector,
struct grub_raid_super_09 *sb,
struct grub_raid_array *array,
grub_disk_addr_t *start_sector)
{
grub_uint32_t *uuid;
if (sb->major_version != 0 || sb->minor_version != 90)
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"unsupported RAID version: %d.%d",
sb->major_version, sb->minor_version);
/* FIXME: Check the checksum. */
/* Multipath. */
if ((int) sb->level == -4)
sb->level = 1;
if (sb->level != 0 && sb->level != 1 && sb->level != 4 &&
sb->level != 5 && sb->level != 6 && sb->level != 10)
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"unsupported RAID level: %d", sb->level);
array->name = NULL;
array->number = sb->md_minor;
array->level = sb->level;
array->layout = sb->layout;
array->total_devs = sb->raid_disks;
array->disk_size = (sb->size) ? sb->size * 2 : sector;
array->chunk_size = sb->chunk_size >> 9;
array->index = 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] = sb->set_uuid0;
uuid[1] = sb->set_uuid1;
uuid[2] = sb->set_uuid2;
uuid[3] = sb->set_uuid3;
*start_sector = 0;
return 0;
}
static grub_err_t
grub_mdraid_detect_1x (grub_disk_t disk, grub_disk_addr_t sector,
struct grub_raid_super_1x *sb,
struct grub_raid_array *array,
grub_disk_addr_t *start_sector)
{
grub_uint64_t sb_size;
struct grub_raid_super_1x *real_sb;
if (sb->major_version != 1)
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"Unsupported RAID version: %d",
sb->major_version);
/* Multipath. */
if ((int) sb->level == -4)
sb->level = 1;
if (sb->level != 0 && sb->level != 1 && sb->level != 4 &&
sb->level != 5 && sb->level != 6 && sb->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);
array->disk_size = grub_le_to_cpu64 (real_sb->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))
array->index = grub_le_to_cpu16
(real_sb->dev_roles[grub_le_to_cpu32 (real_sb->dev_number)]);
else
array->index = 0xffff; /* disk will be later not used! */
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 = real_sb->data_offset;
grub_free (real_sb);
return 0;
}
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_09;
struct grub_raid_super_1x sb_1x;
grub_uint8_t minor_version;
/* The sector where the mdraid 0.90 superblock is stored, if available. */
size = grub_disk_get_size (disk);
sector = NEW_SIZE_SECTORS (size);
if (grub_disk_read (disk, sector, 0, SB_BYTES, &sb_09))
return grub_errno;
/* Look whether there is a mdraid 0.90 superblock. */
if (sb_09.md_magic == SB_MAGIC)
return grub_mdraid_detect_09 (sector, &sb_09, array, start_sector);
/* 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)
{
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_1x))
return grub_errno;
if (sb_1x.magic == SB_MAGIC)
return grub_mdraid_detect_1x (disk, sector, &sb_1x, array,
start_sector);
}
/* Neither 0.90 nor 1.x. */
return grub_error (GRUB_ERR_OUT_OF_RANGE, "not raid");
}
static struct grub_raid grub_mdraid_dev = {
.name = "mdraid",
.detect = grub_mdraid_detect,
.next = 0
};
GRUB_MOD_INIT (mdraid)
{
grub_raid_register (&grub_mdraid_dev);
}
GRUB_MOD_FINI (mdraid)
{
grub_raid_unregister (&grub_mdraid_dev);
}

116
grub-core/disk/memdisk.c Normal file
View file

@ -0,0 +1,116 @@
/* memdisk.c - Access embedded memory disk. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2007,2008 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/kernel.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/types.h>
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";
disk->has_partitions = 0;
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);
}

724
grub-core/disk/raid.c Normal file
View file

@ -0,0 +1,724 @@
/* raid.c - module to read RAID arrays. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008,2009,2010 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
/* 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->device[i])
{
tmp = grub_malloc (sizeof (*tmp));
tmp->disk = array->device[i];
tmp->next = list;
list = tmp;
}
return list;
}
#endif
static grub_err_t
grub_raid_open (const char *name, grub_disk_t disk)
{
struct grub_raid_array *array;
unsigned n;
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->has_partitions = 1;
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->device[k])
{
if (grub_errno == GRUB_ERR_READ_ERROR)
grub_errno = GRUB_ERR_NONE;
err = grub_disk_read (array->device[k],
array->start_sector[k] +
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->device[disknr])
{
/* Reset read error. */
if (grub_errno == GRUB_ERR_READ_ERROR)
grub_errno = GRUB_ERR_NONE;
err = grub_disk_read (array->device[disknr],
array->start_sector[disknr] +
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)
{
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. */
/* 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. */
grub_dprintf ("raid", "array->nr_devs > array->total_devs (%d)?!?",
array->total_devs);
if (array->device[new_array->index] != NULL)
/* We found multiple devices with the same number. Again,
this shouldn't happen. */
grub_dprintf ("raid", "Found two disks with the number %d?!?",
new_array->number);
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;
grub_memset (&array->device, 0, sizeof (array->device));
grub_memset (&array->start_sector, 0, sizeof (array->start_sector));
if (! array->name)
{
for (p = array_list; p != NULL; p = p->next)
{
if (! p->name && 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->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->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);
/* 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->device[new_array->index] = disk;
array->start_sector[new_array->index] = 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;
int i;
p = array;
array = array->next;
for (i = 0; i < GRUB_RAID_MAX_DEVICES; i++)
if (p->device[i])
grub_disk_close (p->device[i]);
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 RAID devices on disk %s\n", name);
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)))
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,
#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 ();
}

View file

@ -0,0 +1,72 @@
/* raid5_recover.c - module to recover from faulty RAID4/5 arrays. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
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->device[i], 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;
}

View file

@ -0,0 +1,219 @@
/* raid6_recover.c - module to recover from faulty RAID6 arrays. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/disk.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/raid.h>
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->device[pos]) &&
(! grub_disk_read (array->device[pos], 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->device[p]) &&
(! grub_disk_read (array->device[p], sector, 0, size, buf)))
{
grub_raid_block_xor (buf, pbuf, size);
goto quit;
}
if (! array->device[q])
{
grub_error (GRUB_ERR_READ_ERROR, "not enough disk to restore");
goto quit;
}
grub_errno = GRUB_ERR_NONE;
if (grub_disk_read (array->device[q], 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->device[p]) || (! array->device[q]))
{
grub_error (GRUB_ERR_READ_ERROR, "not enough disk to restore");
goto quit;
}
if (grub_disk_read (array->device[p], sector, 0, size, buf))
goto quit;
grub_raid_block_xor (pbuf, buf, size);
if (grub_disk_read (array->device[q], 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;
}

610
grub-core/disk/scsi.c Normal file
View file

@ -0,0 +1,610 @@
/* scsi.c - scsi support. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008,2009 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/disk.h>
#include <grub/dl.h>
#include <grub/kernel.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/types.h>
#include <grub/scsi.h>
#include <grub/scsicmd.h>
#include <grub/time.h>
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");
}
if (scsi->devtype == grub_scsi_devtype_cdrom)
disk->has_partitions = 0;
else
disk->has_partitions = 1;
/* 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) == 0))
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"unsupported SCSI block size");
grub_uint32_t sector_mod = 0;
sector = grub_divmod64 (sector, spb, &sector_mod);
if (! (sector_mod == 0 && size % spb == 0))
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
"unaligned SCSI read not supported");
size /= spb;
}
/* Depending on the type, select a read function. */
switch (scsi->devtype)
{
case grub_scsi_devtype_direct:
return grub_scsi_read10 (disk, sector, size, buf);
case grub_scsi_devtype_cdrom:
return grub_scsi_read12 (disk, sector, size, buf);
}
/* XXX: Never reached. */
return GRUB_ERR_NONE;
#if 0 /* Workaround - it works - but very slowly, from some reason
* unknown to me (specially on OHCI). Do not use it. */
/* Split transfer requests to device sector size because */
/* some devices are not able to transfer more than 512-1024 bytes */
grub_err_t err = GRUB_ERR_NONE;
for ( ; size; size--)
{
/* Depending on the type, select a read function. */
switch (scsi->devtype)
{
case grub_scsi_devtype_direct:
err = grub_scsi_read10 (disk, sector, 1, buf);
break;
case grub_scsi_devtype_cdrom:
err = grub_scsi_read12 (disk, sector, 1, buf);
break;
default: /* This should not happen */
return GRUB_ERR_READ_ERROR;
}
if (err)
return err;
sector++;
buf += scsi->blocksize;
}
return err;
#endif
}
static grub_err_t
grub_scsi_write (grub_disk_t disk __attribute((unused)),
grub_disk_addr_t sector __attribute((unused)),
grub_size_t size __attribute((unused)),
const char *buf __attribute((unused)))
{
#if 0
/* XXX: Not tested yet! */
/* XXX: This should depend on the device type? */
return grub_scsi_write10 (disk, sector, size, buf);
#endif
return GRUB_ERR_NOT_IMPLEMENTED_YET;
}
static struct grub_disk_dev grub_scsi_dev =
{
.name = "scsi",
.id = GRUB_DISK_DEVICE_SCSI_ID,
.iterate = grub_scsi_iterate,
.open = grub_scsi_open,
.close = grub_scsi_close,
.read = grub_scsi_read,
.write = grub_scsi_write,
.next = 0
};
GRUB_MOD_INIT(scsi)
{
grub_disk_dev_register (&grub_scsi_dev);
}
GRUB_MOD_FINI(scsi)
{
grub_disk_dev_unregister (&grub_scsi_dev);
}

435
grub-core/disk/usbms.c Normal file
View file

@ -0,0 +1,435 @@
/* usbms.c - USB Mass Storage Support. */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008 Free Software Foundation, Inc.
*
* GRUB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GRUB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
*/
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/usb.h>
#include <grub/scsi.h>
#include <grub/scsicmd.h>
#include <grub/misc.h>
#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)
{
return grub_usb_control_msg (dev, 0x21, 255, 0, interface, 0, 0);
}
static void
grub_usbms_detach (grub_usb_device_t usbdev, int config, int interface)
{
unsigned i;
for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
if (grub_usbms_devices[i] && grub_usbms_devices[i]->dev == usbdev
&& grub_usbms_devices[i]->interface == interface
&& grub_usbms_devices[i]->config == config)
{
grub_free (grub_usbms_devices[i]);
grub_usbms_devices[i] = 0;
}
}
static int
grub_usbms_attach (grub_usb_device_t usbdev, int configno, int interfno)
{
struct grub_usb_desc_if *interf
= usbdev->config[configno].interf[interfno].descif;
int j;
grub_uint8_t luns = 0;
unsigned curnum;
grub_usb_err_t err;
if (first_available_slot == ARRAY_SIZE (grub_usbms_devices))
return 0;
curnum = first_available_slot;
first_available_slot++;
interf = usbdev->config[configno].interf[interfno].descif;
if ((interf->subclass != GRUB_USBMS_SUBCLASS_BULK
/* Experimental support of RBC, MMC-2, UFI, SFF-8070i devices */
&& interf->subclass != GRUB_USBMS_SUBCLASS_RBC
&& interf->subclass != GRUB_USBMS_SUBCLASS_MMC2
&& interf->subclass != GRUB_USBMS_SUBCLASS_UFI
&& interf->subclass != GRUB_USBMS_SUBCLASS_SFF8070 )
|| interf->protocol != GRUB_USBMS_PROTOCOL_BULK)
return 0;
grub_usbms_devices[curnum] = grub_zalloc (sizeof (struct grub_usbms_dev));
if (! grub_usbms_devices[curnum])
return 0;
grub_usbms_devices[curnum]->dev = usbdev;
grub_usbms_devices[curnum]->interface = interfno;
grub_dprintf ("usbms", "alive\n");
/* Iterate over all endpoints of this interface, at least a
IN and OUT bulk endpoint are required. */
for (j = 0; j < interf->endpointcnt; j++)
{
struct grub_usb_desc_endp *endp;
endp = &usbdev->config[0].interf[interfno].descendp[j];
if ((endp->endp_addr & 128) && (endp->attrib & 3) == 2)
{
/* Bulk IN endpoint. */
grub_usbms_devices[curnum]->in = endp;
/* Clear Halt is not possible yet! */
/* grub_usb_clear_halt (usbdev, endp->endp_addr); */
grub_usbms_devices[curnum]->in_maxsz = endp->maxpacket;
}
else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2)
{
/* Bulk OUT endpoint. */
grub_usbms_devices[curnum]->out = endp;
/* Clear Halt is not possible yet! */
/* grub_usb_clear_halt (usbdev, endp->endp_addr); */
grub_usbms_devices[curnum]->out_maxsz = endp->maxpacket;
}
}
if (!grub_usbms_devices[curnum]->in || !grub_usbms_devices[curnum]->out)
{
grub_free (grub_usbms_devices[curnum]);
grub_usbms_devices[curnum] = 0;
return 0;
}
grub_dprintf ("usbms", "alive\n");
/* XXX: Activate the first configuration. */
grub_usb_set_configuration (usbdev, 1);
/* Query the amount of LUNs. */
err = grub_usb_control_msg (usbdev, 0xA1, 254, 0, interfno, 1, (char *) &luns);
if (err)
{
/* In case of a stall, clear the stall. */
if (err == GRUB_USB_ERR_STALL)
{
grub_usb_clear_halt (usbdev, grub_usbms_devices[curnum]->in->endp_addr);
grub_usb_clear_halt (usbdev, grub_usbms_devices[curnum]->out->endp_addr);
}
/* Just set the amount of LUNs to one. */
grub_errno = GRUB_ERR_NONE;
grub_usbms_devices[curnum]->luns = 1;
}
else
/* luns = 0 means one LUN with ID 0 present ! */
/* We get from device not number of LUNs but highest
* LUN number. LUNs are numbered from 0,
* i.e. number of LUNs is luns+1 ! */
grub_usbms_devices[curnum]->luns = luns + 1;
grub_dprintf ("usbms", "alive\n");
usbdev->config[configno].interf[interfno].detach_hook = grub_usbms_detach;
#if 0 /* All this part should be probably deleted.
* This make trouble on some devices if they are not in
* Phase Error state - and there they should be not in such state...
* Bulk only mass storage reset procedure should be used only
* on place and in time when it is really necessary. */
/* Reset recovery procedure */
/* Bulk-Only Mass Storage Reset, after the reset commands
will be accepted. */
grub_usbms_reset (usbdev, i);
grub_usb_clear_halt (usbdev, usbms->in->endp_addr);
grub_usb_clear_halt (usbdev, usbms->out->endp_addr);
#endif
return 1;
}
static int
grub_usbms_iterate (int (*hook) (int bus, int luns))
{
unsigned i;
grub_usb_poll_devices ();
for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
if (grub_usbms_devices[i])
{
if (hook (i, grub_usbms_devices[i]->luns))
return 1;
}
return 0;
}
static grub_err_t
grub_usbms_transfer (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
grub_size_t size, char *buf, int read_write)
{
struct grub_usbms_cbw cbw;
grub_usbms_dev_t dev = (grub_usbms_dev_t) scsi->data;
struct grub_usbms_csw status;
static grub_uint32_t tag = 0;
grub_usb_err_t err = GRUB_USB_ERR_NONE;
grub_usb_err_t errCSW = GRUB_USB_ERR_NONE;
int retrycnt = 3 + 1;
retry:
retrycnt--;
if (retrycnt == 0)
return grub_error (GRUB_ERR_IO, "USB Mass Storage stalled");
/* Setup the request. */
grub_memset (&cbw, 0, sizeof (cbw));
cbw.signature = grub_cpu_to_le32 (0x43425355);
cbw.tag = tag++;
cbw.transfer_length = grub_cpu_to_le32 (size);
cbw.flags = (!read_write) << GRUB_USBMS_DIRECTION_BIT;
cbw.lun = scsi->lun; /* In USB MS CBW are LUN bits on another place than in SCSI CDB, both should be set correctly. */
cbw.length = cmdsize;
grub_memcpy (cbw.cbwcb, cmd, cmdsize);
/* Debug print of CBW content. */
grub_dprintf ("usb", "CBW: sign=0x%08x tag=0x%08x len=0x%08x\n",
cbw.signature, cbw.tag, cbw.transfer_length);
grub_dprintf ("usb", "CBW: flags=0x%02x lun=0x%02x CB_len=0x%02x\n",
cbw.flags, cbw.lun, cbw.length);
grub_dprintf ("usb", "CBW: cmd:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
cbw.cbwcb[ 0], cbw.cbwcb[ 1], cbw.cbwcb[ 2], cbw.cbwcb[ 3],
cbw.cbwcb[ 4], cbw.cbwcb[ 5], cbw.cbwcb[ 6], cbw.cbwcb[ 7],
cbw.cbwcb[ 8], cbw.cbwcb[ 9], cbw.cbwcb[10], cbw.cbwcb[11],
cbw.cbwcb[12], cbw.cbwcb[13], cbw.cbwcb[14], cbw.cbwcb[15]);
/* Write the request.
* XXX: Error recovery is maybe still not fully correct. */
err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr,
sizeof (cbw), (char *) &cbw);
if (err)
{
if (err == GRUB_USB_ERR_STALL)
{
grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
goto CheckCSW;
}
return grub_error (GRUB_ERR_IO, "USB Mass Storage request failed");
}
/* Read/write the data, (maybe) according to specification. */
if (size && (read_write == 0))
{
err = grub_usb_bulk_read (dev->dev, dev->in->endp_addr, size, buf);
grub_dprintf ("usb", "read: %d %d\n", err, GRUB_USB_ERR_STALL);
if (err)
{
if (err == GRUB_USB_ERR_STALL)
grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
goto CheckCSW;
}
/* Debug print of received data. */
grub_dprintf ("usb", "buf:\n");
if (size <= 64)
{
unsigned i;
for (i = 0; i < size; i++)
grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]);
}
else
grub_dprintf ("usb", "Too much data for debug print...\n");
}
else if (size)
{
err = grub_usb_bulk_write (dev->dev, dev->out->endp_addr, size, buf);
grub_dprintf ("usb", "write: %d %d\n", err, GRUB_USB_ERR_STALL);
grub_dprintf ("usb", "First 16 bytes of sent data:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
buf[ 0], buf[ 1], buf[ 2], buf[ 3],
buf[ 4], buf[ 5], buf[ 6], buf[ 7],
buf[ 8], buf[ 9], buf[10], buf[11],
buf[12], buf[13], buf[14], buf[15]);
if (err)
{
if (err == GRUB_USB_ERR_STALL)
grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
goto CheckCSW;
}
/* Debug print of sent data. */
if (size <= 256)
{
unsigned i;
for (i=0; i<size; i++)
grub_dprintf ("usb", "0x%02x: 0x%02x\n", i, buf[i]);
}
else
grub_dprintf ("usb", "Too much data for debug print...\n");
}
/* Read the status - (maybe) according to specification. */
CheckCSW:
errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
sizeof (status), (char *) &status);
if (errCSW)
{
grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
errCSW = grub_usb_bulk_read (dev->dev, dev->in->endp_addr,
sizeof (status), (char *) &status);
if (errCSW)
{ /* Bulk-only reset device. */
grub_dprintf ("usb", "Bulk-only reset device - errCSW\n");
grub_usbms_reset (dev->dev, dev->interface);
grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
goto retry;
}
}
/* Debug print of CSW content. */
grub_dprintf ("usb", "CSW: sign=0x%08x tag=0x%08x resid=0x%08x\n",
status.signature, status.tag, status.residue);
grub_dprintf ("usb", "CSW: status=0x%02x\n", status.status);
/* If phase error or not valid signature, do bulk-only reset device. */
if ((status.status == 2) ||
(status.signature != grub_cpu_to_le32(0x53425355)))
{ /* Bulk-only reset device. */
grub_dprintf ("usb", "Bulk-only reset device - bad status\n");
grub_usbms_reset (dev->dev, dev->interface);
grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
goto retry;
}
/* If "command failed" status or data transfer failed -> error */
if ((status.status || err) && !read_write)
return grub_error (GRUB_ERR_READ_ERROR,
"error communication with USB Mass Storage device");
else if ((status.status || err) && read_write)
return grub_error (GRUB_ERR_WRITE_ERROR,
"error communication with USB Mass Storage device");
return GRUB_ERR_NONE;
}
static grub_err_t
grub_usbms_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
grub_size_t size, char *buf)
{
return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 0);
}
static grub_err_t
grub_usbms_write (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
grub_size_t size, char *buf)
{
return grub_usbms_transfer (scsi, cmdsize, cmd, size, buf, 1);
}
static grub_err_t
grub_usbms_open (int devnum, struct grub_scsi *scsi)
{
grub_usb_poll_devices ();
if (!grub_usbms_devices[devnum])
return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
"unknown USB Mass Storage device");
scsi->data = grub_usbms_devices[devnum];
scsi->luns = grub_usbms_devices[devnum]->luns;
return GRUB_ERR_NONE;
}
static struct grub_scsi_dev grub_usbms_dev =
{
.name = "usb",
.id = GRUB_SCSI_SUBSYSTEM_USBMS,
.iterate = grub_usbms_iterate,
.open = grub_usbms_open,
.read = grub_usbms_read,
.write = grub_usbms_write
};
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);
}