MErge mainline into intwrap
This commit is contained in:
commit
afba9f98ec
719 changed files with 55744 additions and 25714 deletions
917
grub-core/disk/ata.c
Normal file
917
grub-core/disk/ata.c
Normal 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
107
grub-core/disk/ata_pthru.c
Normal 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;
|
||||
}
|
169
grub-core/disk/dmraid_nvidia.c
Normal file
169
grub-core/disk/dmraid_nvidia.c
Normal 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);
|
||||
}
|
851
grub-core/disk/efi/efidisk.c
Normal file
851
grub-core/disk/efi/efidisk.c
Normal 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
96
grub-core/disk/host.c
Normal 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);
|
||||
}
|
629
grub-core/disk/i386/pc/biosdisk.c
Normal file
629
grub-core/disk/i386/pc/biosdisk.c
Normal 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, ®s);
|
||||
|
||||
for (drive = 0; drive < 2; drive++)
|
||||
{
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT | GRUB_CPU_INT_FLAGS_CARRY;
|
||||
regs.edx = drive;
|
||||
|
||||
/* call GET DISK TYPE */
|
||||
regs.eax = 0x1500;
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
|
||||
break;
|
||||
|
||||
/* check if this drive exists */
|
||||
if (!(regs.eax & 0x300))
|
||||
break;
|
||||
}
|
||||
|
||||
return drive;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call IBM/MS INT13 Extensions (int 13 %ah=AH) for DRIVE. DAP
|
||||
* is passed for disk address packet. If an error occurs, return
|
||||
* non-zero, otherwise zero.
|
||||
*/
|
||||
|
||||
static int
|
||||
grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap)
|
||||
{
|
||||
struct grub_bios_int_registers regs;
|
||||
regs.eax = ah << 8;
|
||||
/* compute the address of disk_address_packet */
|
||||
regs.ds = (((grub_addr_t) dap) & 0xffff0000) >> 4;
|
||||
regs.esi = (((grub_addr_t) dap) & 0xffff);
|
||||
regs.edx = drive;
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
return (regs.eax >> 8) & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write
|
||||
* NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs,
|
||||
* return non-zero, otherwise zero.
|
||||
*/
|
||||
static int
|
||||
grub_biosdisk_rw_standard (int ah, int drive, int coff, int hoff,
|
||||
int soff, int nsec, int segment)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
/* Try 3 times. */
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
struct grub_bios_int_registers regs;
|
||||
|
||||
/* set up CHS information */
|
||||
/* set %ch to low eight bits of cylinder */
|
||||
regs.ecx = (coff << 8) & 0xff00;
|
||||
/* set bits 6-7 of %cl to high two bits of cylinder */
|
||||
regs.ecx |= (coff >> 2) & 0xc0;
|
||||
/* set bits 0-5 of %cl to sector */
|
||||
regs.ecx |= soff & 0x3f;
|
||||
|
||||
/* set %dh to head and %dl to drive */
|
||||
regs.edx = (drive & 0xff) | ((hoff << 8) & 0xff00);
|
||||
/* set %ah to AH */
|
||||
regs.eax = (ah << 8) & 0xff00;
|
||||
/* set %al to NSEC */
|
||||
regs.eax |= nsec & 0xff;
|
||||
|
||||
regs.ebx = 0;
|
||||
regs.es = segment;
|
||||
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
/* check if successful */
|
||||
if (!(regs.flags & GRUB_CPU_INT_FLAGS_CARRY))
|
||||
return 0;
|
||||
|
||||
/* save return value */
|
||||
ret = regs.eax >> 8;
|
||||
|
||||
/* if fail, reset the disk system */
|
||||
regs.eax = 0;
|
||||
regs.edx = (drive & 0xff);
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if LBA is supported for DRIVE. If it is supported, then return
|
||||
* the major version of extensions, otherwise zero.
|
||||
*/
|
||||
static int
|
||||
grub_biosdisk_check_int13_extensions (int drive)
|
||||
{
|
||||
struct grub_bios_int_registers regs;
|
||||
|
||||
regs.edx = drive & 0xff;
|
||||
regs.eax = 0x4100;
|
||||
regs.ebx = 0x55aa;
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
|
||||
if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
|
||||
return 0;
|
||||
|
||||
if ((regs.ebx & 0xffff) != 0xaa55)
|
||||
return 0;
|
||||
|
||||
/* check if AH=0x42 is supported */
|
||||
if (!(regs.ecx & 1))
|
||||
return 0;
|
||||
|
||||
return (regs.eax >> 8) & 0xff;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an
|
||||
* error occurs, then return non-zero, otherwise zero.
|
||||
*/
|
||||
static int
|
||||
grub_biosdisk_get_diskinfo_standard (int drive,
|
||||
unsigned long *cylinders,
|
||||
unsigned long *heads,
|
||||
unsigned long *sectors)
|
||||
{
|
||||
struct grub_bios_int_registers regs;
|
||||
|
||||
regs.eax = 0x0800;
|
||||
regs.edx = drive & 0xff;
|
||||
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
|
||||
/* Check if unsuccessful. Ignore return value if carry isn't set to
|
||||
workaround some buggy BIOSes. */
|
||||
if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
|
||||
return (regs.eax & 0xff00) >> 8;
|
||||
|
||||
/* bogus BIOSes may not return an error number */
|
||||
/* 0 sectors means no disk */
|
||||
if (!(regs.ecx & 0x3f))
|
||||
/* XXX 0x60 is one of the unused error numbers */
|
||||
return 0x60;
|
||||
|
||||
/* the number of heads is counted from zero */
|
||||
*heads = ((regs.edx >> 8) & 0xff) + 1;
|
||||
*cylinders = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x0300)) + 1;
|
||||
*sectors = regs.ecx & 0x3f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
grub_biosdisk_get_diskinfo_real (int drive, void *drp, grub_uint16_t ax)
|
||||
{
|
||||
struct grub_bios_int_registers regs;
|
||||
|
||||
regs.eax = ax;
|
||||
|
||||
/* compute the address of drive parameters */
|
||||
regs.esi = ((grub_addr_t) drp) & 0xf;
|
||||
regs.ds = ((grub_addr_t) drp) >> 4;
|
||||
regs.edx = drive & 0xff;
|
||||
|
||||
regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
|
||||
grub_bios_interrupt (0x13, ®s);
|
||||
|
||||
/* Check if unsuccessful. Ignore return value if carry isn't set to
|
||||
workaround some buggy BIOSes. */
|
||||
if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
|
||||
return (regs.eax & 0xff00) >> 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the cdrom information of DRIVE in CDRP. If an error occurs,
|
||||
* then return non-zero, otherwise zero.
|
||||
*/
|
||||
static int
|
||||
grub_biosdisk_get_cdinfo_int13_extensions (int drive, void *cdrp)
|
||||
{
|
||||
return grub_biosdisk_get_diskinfo_real (drive, cdrp, 0x4b01);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the geometry of DRIVE in a drive parameters, DRP. If an error
|
||||
* occurs, then return non-zero, otherwise zero.
|
||||
*/
|
||||
static int
|
||||
grub_biosdisk_get_diskinfo_int13_extensions (int drive, void *drp)
|
||||
{
|
||||
return grub_biosdisk_get_diskinfo_real (drive, drp, 0x4800);
|
||||
}
|
||||
|
||||
static int
|
||||
grub_biosdisk_get_drive (const char *name)
|
||||
{
|
||||
unsigned long drive;
|
||||
|
||||
if ((name[0] != '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 ();
|
||||
}
|
215
grub-core/disk/ieee1275/nand.c
Normal file
215
grub-core/disk/ieee1275/nand.c
Normal 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);
|
||||
}
|
285
grub-core/disk/ieee1275/ofdisk.c
Normal file
285
grub-core/disk/ieee1275/ofdisk.c
Normal 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
241
grub-core/disk/loopback.c
Normal 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
693
grub-core/disk/lvm.c
Normal 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. */
|
||||
}
|
424
grub-core/disk/mdraid_linux.c
Normal file
424
grub-core/disk/mdraid_linux.c
Normal 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
116
grub-core/disk/memdisk.c
Normal 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
724
grub-core/disk/raid.c
Normal 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 ();
|
||||
}
|
72
grub-core/disk/raid5_recover.c
Normal file
72
grub-core/disk/raid5_recover.c
Normal 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;
|
||||
}
|
219
grub-core/disk/raid6_recover.c
Normal file
219
grub-core/disk/raid6_recover.c
Normal 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
610
grub-core/disk/scsi.c
Normal 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, §or_mod);
|
||||
|
||||
if (! (sector_mod == 0 && size % spb == 0))
|
||||
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"unaligned SCSI read not supported");
|
||||
|
||||
size /= spb;
|
||||
}
|
||||
|
||||
/* Depending on the type, select a read function. */
|
||||
switch (scsi->devtype)
|
||||
{
|
||||
case grub_scsi_devtype_direct:
|
||||
return grub_scsi_read10 (disk, sector, size, buf);
|
||||
|
||||
case grub_scsi_devtype_cdrom:
|
||||
return grub_scsi_read12 (disk, sector, size, buf);
|
||||
}
|
||||
|
||||
/* XXX: Never reached. */
|
||||
return GRUB_ERR_NONE;
|
||||
|
||||
#if 0 /* Workaround - it works - but very slowly, from some reason
|
||||
* unknown to me (specially on OHCI). Do not use it. */
|
||||
/* Split transfer requests to device sector size because */
|
||||
/* some devices are not able to transfer more than 512-1024 bytes */
|
||||
grub_err_t err = GRUB_ERR_NONE;
|
||||
|
||||
for ( ; size; size--)
|
||||
{
|
||||
/* Depending on the type, select a read function. */
|
||||
switch (scsi->devtype)
|
||||
{
|
||||
case grub_scsi_devtype_direct:
|
||||
err = grub_scsi_read10 (disk, sector, 1, buf);
|
||||
break;
|
||||
|
||||
case grub_scsi_devtype_cdrom:
|
||||
err = grub_scsi_read12 (disk, sector, 1, buf);
|
||||
break;
|
||||
|
||||
default: /* This should not happen */
|
||||
return GRUB_ERR_READ_ERROR;
|
||||
}
|
||||
if (err)
|
||||
return err;
|
||||
sector++;
|
||||
buf += scsi->blocksize;
|
||||
}
|
||||
|
||||
return err;
|
||||
#endif
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_scsi_write (grub_disk_t disk __attribute((unused)),
|
||||
grub_disk_addr_t sector __attribute((unused)),
|
||||
grub_size_t size __attribute((unused)),
|
||||
const char *buf __attribute((unused)))
|
||||
{
|
||||
#if 0
|
||||
/* XXX: Not tested yet! */
|
||||
|
||||
/* XXX: This should depend on the device type? */
|
||||
return grub_scsi_write10 (disk, sector, size, buf);
|
||||
#endif
|
||||
return GRUB_ERR_NOT_IMPLEMENTED_YET;
|
||||
}
|
||||
|
||||
|
||||
static struct grub_disk_dev grub_scsi_dev =
|
||||
{
|
||||
.name = "scsi",
|
||||
.id = GRUB_DISK_DEVICE_SCSI_ID,
|
||||
.iterate = grub_scsi_iterate,
|
||||
.open = grub_scsi_open,
|
||||
.close = grub_scsi_close,
|
||||
.read = grub_scsi_read,
|
||||
.write = grub_scsi_write,
|
||||
.next = 0
|
||||
};
|
||||
|
||||
GRUB_MOD_INIT(scsi)
|
||||
{
|
||||
grub_disk_dev_register (&grub_scsi_dev);
|
||||
}
|
||||
|
||||
GRUB_MOD_FINI(scsi)
|
||||
{
|
||||
grub_disk_dev_unregister (&grub_scsi_dev);
|
||||
}
|
435
grub-core/disk/usbms.c
Normal file
435
grub-core/disk/usbms.c
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue