summaryrefslogtreecommitdiffstats
path: root/grub-core/disk
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:54:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 10:54:16 +0000
commit485f6ecd453d8a2fd8b9b9fadea03159d8b50797 (patch)
tree32451fa3cdd9321fb2591fada9891b2cb70a9cd1 /grub-core/disk
parentInitial commit. (diff)
downloadgrub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.tar.xz
grub2-485f6ecd453d8a2fd8b9b9fadea03159d8b50797.zip
Adding upstream version 2.06.upstream/2.06upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'grub-core/disk')
-rw-r--r--grub-core/disk/AFSplitter.c95
-rw-r--r--grub-core/disk/ahci.c1163
-rw-r--r--grub-core/disk/arc/arcdisk.c325
-rw-r--r--grub-core/disk/ata.c685
-rw-r--r--grub-core/disk/cryptodisk.c1331
-rw-r--r--grub-core/disk/diskfilter.c1351
-rw-r--r--grub-core/disk/dmraid_nvidia.c196
-rw-r--r--grub-core/disk/efi/efidisk.c902
-rw-r--r--grub-core/disk/geli.c595
-rw-r--r--grub-core/disk/host.c103
-rw-r--r--grub-core/disk/i386/pc/biosdisk.c688
-rw-r--r--grub-core/disk/ieee1275/nand.c242
-rw-r--r--grub-core/disk/ieee1275/obdisk.c1076
-rw-r--r--grub-core/disk/ieee1275/ofdisk.c741
-rw-r--r--grub-core/disk/ldm.c1110
-rw-r--r--grub-core/disk/loopback.c240
-rw-r--r--grub-core/disk/luks.c329
-rw-r--r--grub-core/disk/luks2.c791
-rw-r--r--grub-core/disk/lvm.c1091
-rw-r--r--grub-core/disk/mdraid1x_linux.c233
-rw-r--r--grub-core/disk/mdraid_linux.c298
-rw-r--r--grub-core/disk/mdraid_linux_be.c2
-rw-r--r--grub-core/disk/memdisk.c116
-rw-r--r--grub-core/disk/pata.c556
-rw-r--r--grub-core/disk/raid5_recover.c76
-rw-r--r--grub-core/disk/raid6_recover.c218
-rw-r--r--grub-core/disk/scsi.c766
-rw-r--r--grub-core/disk/uboot/ubootdisk.c307
-rw-r--r--grub-core/disk/usbms.c660
-rw-r--r--grub-core/disk/xen/xendisk.c485
30 files changed, 16771 insertions, 0 deletions
diff --git a/grub-core/disk/AFSplitter.c b/grub-core/disk/AFSplitter.c
new file mode 100644
index 0000000..249163f
--- /dev/null
+++ b/grub-core/disk/AFSplitter.c
@@ -0,0 +1,95 @@
+/*
+ * AFsplitter - Anti forensic information splitter
+ * Copyright 2004, Clemens Fruhwirth <clemens@endorphin.org>
+ *
+ * AFsplitter diffuses information over a large stripe of data,
+ * therefor supporting secure data destruction.
+ *
+ * This program is grub_free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the grub_free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the grub_free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <grub/crypto.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+GRUB_MOD_LICENSE ("GPLv2+");
+
+gcry_err_code_t AF_merge (const gcry_md_spec_t * hash, grub_uint8_t * src,
+ grub_uint8_t * dst, grub_size_t blocksize,
+ grub_size_t blocknumbers);
+
+static void
+diffuse (const gcry_md_spec_t * hash, grub_uint8_t * src,
+ grub_uint8_t * dst, grub_size_t size)
+{
+ grub_size_t i;
+ grub_uint32_t IV; /* host byte order independend hash IV */
+
+ grub_size_t fullblocks = size / hash->mdlen;
+ int padding = size % hash->mdlen;
+ grub_uint8_t final[GRUB_CRYPTO_MAX_MDLEN];
+ grub_uint8_t temp[sizeof (IV) + GRUB_CRYPTO_MAX_MDLEN];
+
+ /* hash block the whole data set with different IVs to produce
+ * more than just a single data block
+ */
+ for (i = 0; i < fullblocks; i++)
+ {
+ IV = grub_cpu_to_be32 (i);
+ grub_memcpy (temp, &IV, sizeof (IV));
+ grub_memcpy (temp + sizeof (IV), src + hash->mdlen * i, hash->mdlen);
+ grub_crypto_hash (hash, dst + hash->mdlen * i, temp,
+ sizeof (IV) + hash->mdlen);
+ }
+
+ if (padding)
+ {
+ IV = grub_cpu_to_be32 (i);
+ grub_memcpy (temp, &IV, sizeof (IV));
+ grub_memcpy (temp + sizeof (IV), src + hash->mdlen * i, padding);
+ grub_crypto_hash (hash, final, temp, sizeof (IV) + padding);
+ grub_memcpy (dst + hash->mdlen * i, final, padding);
+ }
+}
+
+/**
+ * Merges the splitted master key stored on disk into the original key
+ */
+gcry_err_code_t
+AF_merge (const gcry_md_spec_t * hash, grub_uint8_t * src, grub_uint8_t * dst,
+ grub_size_t blocksize, grub_size_t blocknumbers)
+{
+ grub_size_t i;
+ grub_uint8_t *bufblock;
+
+ if (hash->mdlen > GRUB_CRYPTO_MAX_MDLEN || hash->mdlen == 0)
+ return GPG_ERR_INV_ARG;
+
+ bufblock = grub_zalloc (blocksize);
+ if (bufblock == NULL)
+ return GPG_ERR_OUT_OF_MEMORY;
+
+ grub_memset (bufblock, 0, blocksize);
+ for (i = 0; i < blocknumbers - 1; i++)
+ {
+ grub_crypto_xor (bufblock, src + (blocksize * i), bufblock, blocksize);
+ diffuse (hash, bufblock, bufblock, blocksize);
+ }
+ grub_crypto_xor (dst, src + (i * blocksize), bufblock, blocksize);
+
+ grub_free (bufblock);
+ return GPG_ERR_NO_ERROR;
+}
diff --git a/grub-core/disk/ahci.c b/grub-core/disk/ahci.c
new file mode 100644
index 0000000..0e6d56c
--- /dev/null
+++ b/grub-core/disk/ahci.c
@@ -0,0 +1,1163 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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/time.h>
+#include <grub/pci.h>
+#include <grub/ata.h>
+#include <grub/scsi.h>
+#include <grub/misc.h>
+#include <grub/list.h>
+#include <grub/loader.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_ahci_cmd_head
+{
+ grub_uint32_t config;
+ grub_uint32_t transferred;
+ grub_uint64_t command_table_base;
+ grub_uint32_t unused[4];
+};
+
+struct grub_ahci_prdt_entry
+{
+ grub_uint64_t data_base;
+ grub_uint32_t unused;
+ grub_uint32_t size;
+};
+
+struct grub_ahci_cmd_table
+{
+ grub_uint8_t cfis[0x40];
+ grub_uint8_t command[0x10];
+ grub_uint8_t reserved[0x30];
+ struct grub_ahci_prdt_entry prdt[1];
+};
+
+struct grub_ahci_hba_port
+{
+ grub_uint64_t command_list_base;
+ grub_uint64_t fis_base;
+ grub_uint32_t intstatus;
+ grub_uint32_t inten;
+ grub_uint32_t command;
+ grub_uint32_t unused1;
+ grub_uint32_t task_file_data;
+ grub_uint32_t sig;
+ grub_uint32_t status;
+ grub_uint32_t unused2;
+ grub_uint32_t sata_error;
+ grub_uint32_t sata_active;
+ grub_uint32_t command_issue;
+ grub_uint32_t unused3;
+ grub_uint32_t fbs;
+ grub_uint32_t unused4[15];
+};
+
+enum grub_ahci_hba_port_command
+ {
+ GRUB_AHCI_HBA_PORT_CMD_ST = 0x01,
+ GRUB_AHCI_HBA_PORT_CMD_SPIN_UP = 0x02,
+ GRUB_AHCI_HBA_PORT_CMD_POWER_ON = 0x04,
+ GRUB_AHCI_HBA_PORT_CMD_FRE = 0x10,
+ GRUB_AHCI_HBA_PORT_CMD_CR = 0x8000,
+ GRUB_AHCI_HBA_PORT_CMD_FR = 0x4000,
+ };
+
+enum grub_ahci_hba_port_int_status
+ {
+ GRUB_AHCI_HBA_PORT_IS_IFS = (1UL << 27),
+ GRUB_AHCI_HBA_PORT_IS_HBDS = (1UL << 28),
+ GRUB_AHCI_HBA_PORT_IS_HBFS = (1UL << 29),
+ GRUB_AHCI_HBA_PORT_IS_TFES = (1UL << 30),
+ };
+
+#define GRUB_AHCI_HBA_PORT_IS_FATAL_MASK ( \
+ GRUB_AHCI_HBA_PORT_IS_IFS | \
+ GRUB_AHCI_HBA_PORT_IS_HBDS | \
+ GRUB_AHCI_HBA_PORT_IS_HBFS | \
+ GRUB_AHCI_HBA_PORT_IS_TFES)
+
+struct grub_ahci_hba
+{
+ grub_uint32_t cap;
+ grub_uint32_t global_control;
+ grub_uint32_t intr_status;
+ grub_uint32_t ports_implemented;
+ grub_uint32_t unused1[6];
+ grub_uint32_t bios_handoff;
+ grub_uint32_t unused2[53];
+ struct grub_ahci_hba_port ports[32];
+};
+
+struct grub_ahci_received_fis
+{
+ char raw[4096];
+};
+
+enum
+ {
+ GRUB_AHCI_HBA_CAP_NPORTS_MASK = 0x1f
+ };
+
+enum
+ {
+ GRUB_AHCI_HBA_GLOBAL_CONTROL_RESET = 0x00000001,
+ GRUB_AHCI_HBA_GLOBAL_CONTROL_INTR_EN = 0x00000002,
+ GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN = 0x80000000,
+ };
+
+enum
+ {
+ GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED = 1,
+ GRUB_AHCI_BIOS_HANDOFF_OS_OWNED = 2,
+ GRUB_AHCI_BIOS_HANDOFF_OS_OWNERSHIP_CHANGED = 8,
+ GRUB_AHCI_BIOS_HANDOFF_RWC = 8
+ };
+
+
+struct grub_ahci_device
+{
+ struct grub_ahci_device *next;
+ struct grub_ahci_device **prev;
+ volatile struct grub_ahci_hba *hba;
+ int port;
+ int num;
+ struct grub_pci_dma_chunk *command_list_chunk;
+ volatile struct grub_ahci_cmd_head *command_list;
+ struct grub_pci_dma_chunk *command_table_chunk;
+ volatile struct grub_ahci_cmd_table *command_table;
+ struct grub_pci_dma_chunk *rfis;
+ int present;
+ int atapi;
+};
+
+static grub_err_t
+grub_ahci_readwrite_real (struct grub_ahci_device *dev,
+ struct grub_disk_ata_pass_through_parms *parms,
+ int spinup, int reset);
+
+
+enum
+ {
+ GRUB_AHCI_CONFIG_READ = 0,
+ GRUB_AHCI_CONFIG_CFIS_LENGTH_MASK = 0x1f,
+ GRUB_AHCI_CONFIG_ATAPI = 0x20,
+ GRUB_AHCI_CONFIG_WRITE = 0x40,
+ GRUB_AHCI_CONFIG_PREFETCH = 0x80,
+ GRUB_AHCI_CONFIG_RESET = 0x100,
+ GRUB_AHCI_CONFIG_BIST = 0x200,
+ GRUB_AHCI_CONFIG_CLEAR_R_OK = 0x400,
+ GRUB_AHCI_CONFIG_PMP_MASK = 0xf000,
+ GRUB_AHCI_CONFIG_PRDT_LENGTH_MASK = 0xffff0000,
+ };
+#define GRUB_AHCI_CONFIG_CFIS_LENGTH_SHIFT 0
+#define GRUB_AHCI_CONFIG_PMP_SHIFT 12
+#define GRUB_AHCI_CONFIG_PRDT_LENGTH_SHIFT 16
+#define GRUB_AHCI_INTERRUPT_ON_COMPLETE 0x80000000
+
+#define GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH 0x200000
+
+static struct grub_ahci_device *grub_ahci_devices;
+static int numdevs;
+
+static int
+grub_ahci_pciinit (grub_pci_device_t dev,
+ grub_pci_id_t pciid __attribute__ ((unused)),
+ void *data __attribute__ ((unused)))
+{
+ grub_pci_address_t addr;
+ grub_uint32_t class;
+ grub_uint32_t bar;
+ unsigned i, nports;
+ volatile struct grub_ahci_hba *hba;
+
+ /* Read class. */
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+ class = grub_pci_read (addr);
+
+ /* Check if this class ID matches that of a PCI IDE Controller. */
+ if (class >> 8 != 0x010601)
+ return 0;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG5);
+
+ bar = grub_pci_read (addr);
+
+ if ((bar & (GRUB_PCI_ADDR_SPACE_MASK | GRUB_PCI_ADDR_MEM_TYPE_MASK
+ | GRUB_PCI_ADDR_MEM_PREFETCH))
+ != (GRUB_PCI_ADDR_SPACE_MEMORY | GRUB_PCI_ADDR_MEM_TYPE_32))
+ return 0;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word (addr, grub_pci_read_word (addr)
+ | GRUB_PCI_COMMAND_MEM_ENABLED | GRUB_PCI_COMMAND_BUS_MASTER);
+
+ hba = grub_pci_device_map_range (dev, bar & GRUB_PCI_ADDR_MEM_MASK,
+ sizeof (*hba));
+ grub_dprintf ("ahci", "dev: %x:%x.%x\n", dev.bus, dev.device, dev.function);
+
+ grub_dprintf ("ahci", "tfd[0]: %x\n",
+ hba->ports[0].task_file_data);
+ grub_dprintf ("ahci", "cmd[0]: %x\n",
+ hba->ports[0].command);
+ grub_dprintf ("ahci", "st[0]: %x\n",
+ hba->ports[0].status);
+ grub_dprintf ("ahci", "err[0]: %x\n",
+ hba->ports[0].sata_error);
+
+ grub_dprintf ("ahci", "tfd[1]: %x\n",
+ hba->ports[1].task_file_data);
+ grub_dprintf ("ahci", "cmd[1]: %x\n",
+ hba->ports[1].command);
+ grub_dprintf ("ahci", "st[1]: %x\n",
+ hba->ports[1].status);
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ hba->ports[1].sata_error = hba->ports[1].sata_error;
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ grub_dprintf ("ahci", "BH:%x\n", hba->bios_handoff);
+
+ if (! (hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_OS_OWNED))
+ {
+ grub_uint64_t endtime;
+
+ grub_dprintf ("ahci", "Requesting AHCI ownership\n");
+ hba->bios_handoff = (hba->bios_handoff & ~GRUB_AHCI_BIOS_HANDOFF_RWC)
+ | GRUB_AHCI_BIOS_HANDOFF_OS_OWNED;
+ grub_dprintf ("ahci", "Waiting for BIOS to give up ownership\n");
+ endtime = grub_get_time_ms () + 1000;
+ while ((hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED)
+ && grub_get_time_ms () < endtime);
+ if (hba->bios_handoff & GRUB_AHCI_BIOS_HANDOFF_BIOS_OWNED)
+ {
+ grub_dprintf ("ahci", "Forcibly taking ownership\n");
+ hba->bios_handoff = GRUB_AHCI_BIOS_HANDOFF_OS_OWNED;
+ hba->bios_handoff |= GRUB_AHCI_BIOS_HANDOFF_OS_OWNERSHIP_CHANGED;
+ }
+ else
+ grub_dprintf ("ahci", "AHCI ownership obtained\n");
+ }
+ else
+ grub_dprintf ("ahci", "AHCI is already in OS mode\n");
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ if (!(hba->global_control & GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN))
+ grub_dprintf ("ahci", "AHCI is in compat mode. Switching\n");
+ else
+ grub_dprintf ("ahci", "AHCI is in AHCI mode.\n");
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ /* {
+ grub_uint64_t endtime;
+ hba->global_control |= 1;
+ endtime = grub_get_time_ms () + 1000;
+ while (hba->global_control & 1)
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't reset AHCI\n");
+ return 0;
+ }
+ }*/
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ for (i = 0; i < 5; i++)
+ {
+ hba->global_control |= GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN;
+ grub_millisleep (1);
+ if (hba->global_control & GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN)
+ break;
+ }
+ if (i == 5)
+ {
+ grub_dprintf ("ahci", "Couldn't put AHCI in AHCI mode\n");
+ return 0;
+ }
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ for (i = 0; i < 5; i++)
+ {
+ hba->global_control |= GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN;
+ grub_millisleep (1);
+ if (hba->global_control & GRUB_AHCI_HBA_GLOBAL_CONTROL_AHCI_EN)
+ break;
+ }
+ if (i == 5)
+ {
+ grub_dprintf ("ahci", "Couldn't put AHCI in AHCI mode\n");
+ return 0;
+ }
+
+ grub_dprintf ("ahci", "err[1]: %x\n",
+ hba->ports[1].sata_error);
+
+ grub_dprintf ("ahci", "GLC:%x\n", hba->global_control);
+
+ nports = (GRUB_AHCI_HBA_CAP_NPORTS_MASK) + 1;
+
+ grub_dprintf ("ahci", "%d AHCI ports, PI = 0x%x\n", nports,
+ hba->ports_implemented);
+
+ struct grub_ahci_device *adevs[GRUB_AHCI_HBA_CAP_NPORTS_MASK + 1];
+ struct grub_ahci_device *failed_adevs[GRUB_AHCI_HBA_CAP_NPORTS_MASK + 1];
+ grub_uint32_t fr_running = 0;
+
+ for (i = 0; i < nports; i++)
+ failed_adevs[i] = 0;
+ for (i = 0; i < nports; i++)
+ {
+ if (!(hba->ports_implemented & (1 << i)))
+ {
+ adevs[i] = 0;
+ continue;
+ }
+
+ adevs[i] = grub_zalloc (sizeof (*adevs[i]));
+ if (!adevs[i])
+ return 1;
+
+ adevs[i]->hba = hba;
+ adevs[i]->port = i;
+ adevs[i]->present = 1;
+ adevs[i]->num = numdevs++;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ adevs[i]->hba->ports[adevs[i]->port].sata_error = adevs[i]->hba->ports[adevs[i]->port].sata_error;
+ grub_dprintf ("ahci", "port: %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ adevs[i]->command_list_chunk = grub_memalign_dma32 (1024, sizeof (struct grub_ahci_cmd_head) * 32);
+ if (!adevs[i]->command_list_chunk)
+ {
+ adevs[i] = 0;
+ continue;
+ }
+
+ adevs[i]->command_table_chunk = grub_memalign_dma32 (1024,
+ sizeof (struct grub_ahci_cmd_table));
+ if (!adevs[i]->command_table_chunk)
+ {
+ grub_dma_free (adevs[i]->command_list_chunk);
+ adevs[i] = 0;
+ continue;
+ }
+
+ adevs[i]->command_list = grub_dma_get_virt (adevs[i]->command_list_chunk);
+ adevs[i]->command_table = grub_dma_get_virt (adevs[i]->command_table_chunk);
+
+ grub_memset ((void *) adevs[i]->command_list, 0,
+ sizeof (struct grub_ahci_cmd_table));
+ grub_memset ((void *) adevs[i]->command_table, 0,
+ sizeof (struct grub_ahci_cmd_head) * 32);
+
+ adevs[i]->command_list->command_table_base
+ = grub_dma_get_phys (adevs[i]->command_table_chunk);
+
+ grub_dprintf ("ahci", "found device ahci%d (port %d), command_table = %p, command_list = %p\n",
+ adevs[i]->num, adevs[i]->port, grub_dma_get_virt (adevs[i]->command_table_chunk),
+ grub_dma_get_virt (adevs[i]->command_list_chunk));
+
+ adevs[i]->hba->ports[adevs[i]->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ }
+
+ grub_uint64_t endtime;
+ endtime = grub_get_time_ms () + 1000;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ {
+ grub_dprintf ("ahci", "couldn't stop FR on port %d\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ adevs[i]->hba->ports[adevs[i]->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_ST;
+ endtime = grub_get_time_ms () + 1000;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ {
+ grub_dprintf ("ahci", "couldn't stop CR on port %d\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ adevs[i]->hba->ports[adevs[i]->port].inten = 0;
+ adevs[i]->hba->ports[adevs[i]->port].intstatus = ~0;
+ // adevs[i]->hba->ports[adevs[i]->port].fbs = 0;
+
+ grub_dprintf ("ahci", "port: %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ adevs[i]->rfis = grub_memalign_dma32 (4096,
+ sizeof (struct grub_ahci_received_fis));
+ grub_memset ((char *) grub_dma_get_virt (adevs[i]->rfis), 0,
+ sizeof (struct grub_ahci_received_fis));
+ grub_memset ((char *) grub_dma_get_virt (adevs[i]->command_list_chunk), 0,
+ sizeof (struct grub_ahci_cmd_head));
+ grub_memset ((char *) grub_dma_get_virt (adevs[i]->command_table_chunk), 0,
+ sizeof (struct grub_ahci_cmd_table));
+ adevs[i]->hba->ports[adevs[i]->port].fis_base = grub_dma_get_phys (adevs[i]->rfis);
+ adevs[i]->hba->ports[adevs[i]->port].command_list_base
+ = grub_dma_get_phys (adevs[i]->command_list_chunk);
+ adevs[i]->hba->ports[adevs[i]->port].command_issue = 0;
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_FRE;
+ }
+
+ endtime = grub_get_time_ms () + 1000;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && !(adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && !(adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ {
+ grub_dprintf ("ahci", "couldn't start FR on port %d\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ grub_dprintf ("ahci", "port: %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+ fr_running |= (1 << i);
+
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_SPIN_UP;
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_POWER_ON;
+ adevs[i]->hba->ports[adevs[i]->port].command |= 1 << 28;
+
+ grub_dprintf ("ahci", "port: %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+ }
+
+ /* 10ms should actually be enough. */
+ endtime = grub_get_time_ms () + 100;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].status & 7) != 3)
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].status & 7) != 3)
+ {
+ grub_dprintf ("ahci", "couldn't detect device on port %d\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ grub_dprintf ("ahci", "port %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_POWER_ON;
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_SPIN_UP;
+
+ grub_dprintf ("ahci", "port %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ adevs[i]->hba->ports[adevs[i]->port].sata_error = ~0;
+ grub_dprintf ("ahci", "port %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ grub_dprintf ("ahci", "port %d, offset: %x, tfd:%x, CMD: %x\n", adevs[i]->port,
+ (int) ((char *) &adevs[i]->hba->ports[adevs[i]->port].task_file_data -
+ (char *) adevs[i]->hba),
+ adevs[i]->hba->ports[adevs[i]->port].task_file_data,
+ adevs[i]->hba->ports[adevs[i]->port].command);
+
+ grub_dprintf ("ahci", "port %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+ }
+
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ grub_dprintf ("ahci", "port %d, offset: %x, tfd:%x, CMD: %x\n", adevs[i]->port,
+ (int) ((char *) &adevs[i]->hba->ports[adevs[i]->port].task_file_data -
+ (char *) adevs[i]->hba),
+ adevs[i]->hba->ports[adevs[i]->port].task_file_data,
+ adevs[i]->hba->ports[adevs[i]->port].command);
+
+ grub_dprintf ("ahci", "port: %d, err: %x\n", adevs[i]->port,
+ adevs[i]->hba->ports[adevs[i]->port].sata_error);
+
+ adevs[i]->hba->ports[adevs[i]->port].command
+ = (adevs[i]->hba->ports[adevs[i]->port].command & 0x0fffffff) | (1 << 28)
+ | GRUB_AHCI_HBA_PORT_CMD_SPIN_UP
+ | GRUB_AHCI_HBA_PORT_CMD_POWER_ON;
+
+ /* struct grub_disk_ata_pass_through_parms parms2;
+ grub_memset (&parms2, 0, sizeof (parms2));
+ parms2.taskfile.cmd = 8;
+ grub_ahci_readwrite_real (dev, &parms2, 1, 1);*/
+ }
+
+ endtime = grub_get_time_ms () + 32000;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].task_file_data & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ)))
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].task_file_data & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ)))
+ {
+ grub_dprintf ("ahci", "port %d is busy\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ adevs[i]->hba->ports[adevs[i]->port].command |= GRUB_AHCI_HBA_PORT_CMD_ST;
+
+ endtime = grub_get_time_ms () + 1000;
+
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && !(adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ break;
+ if (i == nports)
+ break;
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && !(adevs[i]->hba->ports[adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ {
+ grub_dprintf ("ahci", "couldn't start CR on port %d\n", i);
+ failed_adevs[i] = adevs[i];
+ adevs[i] = 0;
+ }
+
+ grub_dprintf ("ahci", "cleaning up failed devs\n");
+
+ for (i = 0; i < nports; i++)
+ if (failed_adevs[i] && (fr_running & (1 << i)))
+ failed_adevs[i]->hba->ports[failed_adevs[i]->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+
+ endtime = grub_get_time_ms () + 1000;
+ while (grub_get_time_ms () < endtime)
+ {
+ for (i = 0; i < nports; i++)
+ if (failed_adevs[i] && (fr_running & (1 << i)) && (failed_adevs[i]->hba->ports[failed_adevs[i]->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ break;
+ if (i == nports)
+ break;
+ }
+ for (i = 0; i < nports; i++)
+ if (failed_adevs[i])
+ {
+ grub_dma_free (failed_adevs[i]->command_list_chunk);
+ grub_dma_free (failed_adevs[i]->command_table_chunk);
+ grub_dma_free (failed_adevs[i]->rfis);
+ }
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i] && (adevs[i]->hba->ports[adevs[i]->port].sig >> 16) == 0xeb14)
+ adevs[i]->atapi = 1;
+
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word (addr, grub_pci_read_word (addr)
+ | GRUB_PCI_COMMAND_BUS_MASTER);
+
+ for (i = 0; i < nports; i++)
+ if (adevs[i])
+ {
+ grub_list_push (GRUB_AS_LIST_P (&grub_ahci_devices),
+ GRUB_AS_LIST (adevs[i]));
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_ahci_initialize (void)
+{
+ grub_pci_iterate (grub_ahci_pciinit, NULL);
+ return grub_errno;
+}
+
+static grub_err_t
+grub_ahci_fini_hw (int noreturn __attribute__ ((unused)))
+{
+ struct grub_ahci_device *dev;
+
+ for (dev = grub_ahci_devices; dev; dev = dev->next)
+ {
+ grub_uint64_t endtime;
+
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop FR\n");
+ break;
+ }
+
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_ST;
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop CR\n");
+ break;
+ }
+ grub_dma_free (dev->command_list_chunk);
+ grub_dma_free (dev->command_table_chunk);
+ grub_dma_free (dev->rfis);
+ dev->command_list_chunk = NULL;
+ dev->command_table_chunk = NULL;
+ dev->rfis = NULL;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static int
+reinit_port (struct grub_ahci_device *dev)
+{
+ struct grub_pci_dma_chunk *command_list;
+ struct grub_pci_dma_chunk *command_table;
+ grub_uint64_t endtime;
+
+ command_list = grub_memalign_dma32 (1024, sizeof (struct grub_ahci_cmd_head));
+ if (!command_list)
+ return 1;
+
+ command_table = grub_memalign_dma32 (1024,
+ sizeof (struct grub_ahci_cmd_table));
+ if (!command_table)
+ {
+ grub_dma_free (command_list);
+ return 1;
+ }
+
+ grub_dprintf ("ahci", "found device ahci%d (port %d)\n", dev->num, dev->port);
+
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop FR\n");
+ goto out;
+ }
+
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_ST;
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop CR\n");
+ goto out;
+ }
+
+ dev->hba->ports[dev->port].fbs = 2;
+
+ dev->rfis = grub_memalign_dma32 (4096,
+ sizeof (struct grub_ahci_received_fis));
+ grub_memset ((char *) grub_dma_get_virt (dev->rfis), 0,
+ sizeof (struct grub_ahci_received_fis));
+ dev->hba->ports[dev->port].fis_base = grub_dma_get_phys (dev->rfis);
+ dev->hba->ports[dev->port].command_list_base
+ = grub_dma_get_phys (command_list);
+ dev->hba->ports[dev->port].command |= GRUB_AHCI_HBA_PORT_CMD_FRE;
+ while (!(dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't start FR\n");
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ goto out;
+ }
+ dev->hba->ports[dev->port].command |= GRUB_AHCI_HBA_PORT_CMD_ST;
+ while (!(dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't start CR\n");
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_CR;
+ goto out_stop_fr;
+ }
+
+ dev->hba->ports[dev->port].command
+ = (dev->hba->ports[dev->port].command & 0x0fffffff) | (1 << 28) | 2 | 4;
+
+ dev->command_list_chunk = command_list;
+ dev->command_list = grub_dma_get_virt (command_list);
+ dev->command_table_chunk = command_table;
+ dev->command_table = grub_dma_get_virt (command_table);
+ dev->command_list->command_table_base
+ = grub_dma_get_phys (command_table);
+
+ return 0;
+ out_stop_fr:
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_FR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop FR\n");
+ break;
+ }
+ out:
+ grub_dma_free (command_list);
+ grub_dma_free (command_table);
+ grub_dma_free (dev->rfis);
+ return 1;
+}
+
+static grub_err_t
+grub_ahci_restore_hw (void)
+{
+ struct grub_ahci_device **pdev;
+
+ for (pdev = &grub_ahci_devices; *pdev; pdev = &((*pdev)->next))
+ if (reinit_port (*pdev))
+ {
+ struct grub_ahci_device *odev;
+ odev = *pdev;
+ *pdev = (*pdev)->next;
+ grub_free (odev);
+ }
+ return GRUB_ERR_NONE;
+}
+
+
+
+
+static int
+grub_ahci_iterate (grub_ata_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_ahci_device *dev;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ FOR_LIST_ELEMENTS(dev, grub_ahci_devices)
+ if (hook (GRUB_SCSI_SUBSYSTEM_AHCI, dev->num, hook_data))
+ return 1;
+
+ return 0;
+}
+
+#if 0
+static int
+find_free_cmd_slot (struct grub_ahci_device *dev)
+{
+ int i;
+ for (i = 0; i < 32; i++)
+ {
+ if (dev->hda->ports[dev->port].command_issue & (1 << i))
+ continue;
+ if (dev->hda->ports[dev->port].sata_active & (1 << i))
+ continue;
+ return i;
+ }
+ return -1;
+}
+#endif
+
+enum
+ {
+ GRUB_AHCI_FIS_REG_H2D = 0x27
+ };
+
+static const int register_map[11] = { 3 /* Features */,
+ 12 /* Sectors */,
+ 4 /* LBA low */,
+ 5 /* LBA mid */,
+ 6 /* LBA high */,
+ 7 /* Device */,
+ 2 /* CMD register */,
+ 13 /* Sectors 48 */,
+ 8 /* LBA48 low */,
+ 9 /* LBA48 mid */,
+ 10 /* LBA48 high */ };
+
+static grub_err_t
+grub_ahci_reset_port (struct grub_ahci_device *dev, int force)
+{
+ grub_uint64_t endtime;
+
+ dev->hba->ports[dev->port].sata_error = dev->hba->ports[dev->port].sata_error;
+
+ if (force || (dev->hba->ports[dev->port].command_issue & 1)
+ || (dev->hba->ports[dev->port].task_file_data & 0x80))
+ {
+ struct grub_disk_ata_pass_through_parms parms2;
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_ST;
+ dev->hba->ports[dev->port].command_issue = 0;
+ dev->command_list[0].config = 0;
+ dev->command_table[0].prdt[0].unused = 0;
+ dev->command_table[0].prdt[0].size = 0;
+ dev->command_table[0].prdt[0].data_base = 0;
+
+ endtime = grub_get_time_ms () + 1000;
+ while ((dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop CR");
+ return grub_error (GRUB_ERR_IO, "couldn't stop CR");
+ }
+ dev->hba->ports[dev->port].command |= 8;
+ while (dev->hba->ports[dev->port].command & 8)
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't set CLO\n");
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_FRE;
+ return grub_error (GRUB_ERR_IO, "couldn't set CLO");
+ }
+
+ dev->hba->ports[dev->port].command |= GRUB_AHCI_HBA_PORT_CMD_ST;
+ while (!(dev->hba->ports[dev->port].command & GRUB_AHCI_HBA_PORT_CMD_CR))
+ if (grub_get_time_ms () > endtime)
+ {
+ grub_dprintf ("ahci", "couldn't stop CR");
+ dev->hba->ports[dev->port].command &= ~GRUB_AHCI_HBA_PORT_CMD_ST;
+ return grub_error (GRUB_ERR_IO, "couldn't stop CR");
+ }
+ dev->hba->ports[dev->port].sata_error = dev->hba->ports[dev->port].sata_error;
+ grub_memset (&parms2, 0, sizeof (parms2));
+ parms2.taskfile.cmd = 8;
+ return grub_ahci_readwrite_real (dev, &parms2, 1, 1);
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ahci_readwrite_real (struct grub_ahci_device *dev,
+ struct grub_disk_ata_pass_through_parms *parms,
+ int spinup, int reset)
+{
+ struct grub_pci_dma_chunk *bufc;
+ grub_uint64_t endtime;
+ unsigned i;
+ grub_err_t err = GRUB_ERR_NONE;
+
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ if (!reset)
+ grub_ahci_reset_port (dev, 0);
+
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+ dev->hba->ports[dev->port].task_file_data = 0;
+ dev->hba->ports[dev->port].command_issue = 0;
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ dev->hba->ports[dev->port].sata_error = dev->hba->ports[dev->port].sata_error;
+
+ grub_dprintf("ahci", "grub_ahci_read (size=%llu, cmdsize = %llu)\n",
+ (unsigned long long) parms->size,
+ (unsigned long long) parms->cmdsize);
+
+ if (parms->cmdsize != 0 && parms->cmdsize != 12 && parms->cmdsize != 16)
+ return grub_error (GRUB_ERR_BUG, "incorrect ATAPI command size");
+
+ if (parms->size > GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH)
+ return grub_error (GRUB_ERR_BUG, "too big data buffer");
+
+ if (parms->size)
+ bufc = grub_memalign_dma32 (1024, parms->size + (parms->size & 1));
+ else
+ bufc = grub_memalign_dma32 (1024, 512);
+
+ grub_dprintf ("ahci", "AHCI tfd = %x, CL=%p\n",
+ dev->hba->ports[dev->port].task_file_data,
+ dev->command_list);
+ /* FIXME: support port multipliers. */
+ dev->command_list[0].config
+ = (5 << GRUB_AHCI_CONFIG_CFIS_LENGTH_SHIFT)
+ // | GRUB_AHCI_CONFIG_CLEAR_R_OK
+ | (0 << GRUB_AHCI_CONFIG_PMP_SHIFT)
+ | ((parms->size ? 1 : 0) << GRUB_AHCI_CONFIG_PRDT_LENGTH_SHIFT)
+ | (parms->cmdsize ? GRUB_AHCI_CONFIG_ATAPI : 0)
+ | (parms->write ? GRUB_AHCI_CONFIG_WRITE : GRUB_AHCI_CONFIG_READ)
+ | (parms->taskfile.cmd == 8 ? (1 << 8) : 0);
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ dev->command_list[0].transferred = 0;
+ dev->command_list[0].command_table_base
+ = grub_dma_get_phys (dev->command_table_chunk);
+
+ grub_memset ((char *) dev->command_list[0].unused, 0,
+ sizeof (dev->command_list[0].unused));
+
+ grub_memset ((char *) &dev->command_table[0], 0,
+ sizeof (dev->command_table[0]));
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ if (parms->cmdsize)
+ grub_memcpy ((char *) dev->command_table[0].command, parms->cmd,
+ parms->cmdsize);
+
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ dev->command_table[0].cfis[0] = GRUB_AHCI_FIS_REG_H2D;
+ dev->command_table[0].cfis[1] = 0x80;
+ for (i = 0; i < sizeof (parms->taskfile.raw); i++)
+ dev->command_table[0].cfis[register_map[i]] = parms->taskfile.raw[i];
+
+ grub_dprintf ("ahci", "cfis: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ dev->command_table[0].cfis[0], dev->command_table[0].cfis[1],
+ dev->command_table[0].cfis[2], dev->command_table[0].cfis[3],
+ dev->command_table[0].cfis[4], dev->command_table[0].cfis[5],
+ dev->command_table[0].cfis[6], dev->command_table[0].cfis[7]);
+ grub_dprintf ("ahci", "cfis: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ dev->command_table[0].cfis[8], dev->command_table[0].cfis[9],
+ dev->command_table[0].cfis[10], dev->command_table[0].cfis[11],
+ dev->command_table[0].cfis[12], dev->command_table[0].cfis[13],
+ dev->command_table[0].cfis[14], dev->command_table[0].cfis[15]);
+
+ dev->command_table[0].prdt[0].data_base = grub_dma_get_phys (bufc);
+ dev->command_table[0].prdt[0].unused = 0;
+ dev->command_table[0].prdt[0].size = (parms->size - 1);
+
+ grub_dprintf ("ahci", "PRDT = %" PRIxGRUB_UINT64_T ", %x, %x (%"
+ PRIuGRUB_SIZE ")\n",
+ dev->command_table[0].prdt[0].data_base,
+ dev->command_table[0].prdt[0].unused,
+ dev->command_table[0].prdt[0].size,
+ (grub_size_t) ((char *) &dev->command_table[0].prdt[0]
+ - (char *) &dev->command_table[0]));
+
+ if (parms->write)
+ grub_memcpy ((char *) grub_dma_get_virt (bufc), parms->buffer, parms->size);
+
+ grub_dprintf ("ahci", "AHCI command scheduled\n");
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+ grub_dprintf ("ahci", "AHCI inten = %x\n",
+ dev->hba->ports[dev->port].inten);
+ grub_dprintf ("ahci", "AHCI intstatus = %x\n",
+ dev->hba->ports[dev->port].intstatus);
+
+ dev->hba->ports[dev->port].inten = 0xffffffff;//(1 << 2) | (1 << 5);
+ dev->hba->ports[dev->port].intstatus = 0xffffffff;//(1 << 2) | (1 << 5);
+ grub_dprintf ("ahci", "AHCI inten = %x\n",
+ dev->hba->ports[dev->port].inten);
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+ dev->hba->ports[dev->port].sata_active = 1;
+ dev->hba->ports[dev->port].command_issue = 1;
+ grub_dprintf ("ahci", "AHCI sig = %x\n", dev->hba->ports[dev->port].sig);
+ grub_dprintf ("ahci", "AHCI tfd = %x\n",
+ dev->hba->ports[dev->port].task_file_data);
+
+ endtime = grub_get_time_ms () + (spinup ? 20000 : 20000);
+ while ((dev->hba->ports[dev->port].command_issue & 1))
+ if (grub_get_time_ms () > endtime ||
+ (dev->hba->ports[dev->port].intstatus & GRUB_AHCI_HBA_PORT_IS_FATAL_MASK))
+ {
+ grub_dprintf ("ahci", "AHCI status <%x %x %x %x>\n",
+ dev->hba->ports[dev->port].command_issue,
+ dev->hba->ports[dev->port].sata_active,
+ dev->hba->ports[dev->port].intstatus,
+ dev->hba->ports[dev->port].task_file_data);
+ dev->hba->ports[dev->port].command_issue = 0;
+ if (dev->hba->ports[dev->port].intstatus & GRUB_AHCI_HBA_PORT_IS_FATAL_MASK)
+ err = grub_error (GRUB_ERR_IO, "AHCI transfer error");
+ else
+ err = grub_error (GRUB_ERR_IO, "AHCI transfer timed out");
+ if (!reset)
+ grub_ahci_reset_port (dev, 1);
+ break;
+ }
+
+ grub_dprintf ("ahci", "AHCI command completed <%x %x %x %x %x, %x %x>\n",
+ dev->hba->ports[dev->port].command_issue,
+ dev->hba->ports[dev->port].intstatus,
+ dev->hba->ports[dev->port].task_file_data,
+ dev->command_list[0].transferred,
+ dev->hba->ports[dev->port].sata_error,
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x00],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x18]);
+ grub_dprintf ("ahci",
+ "last PIO FIS %08x %08x %08x %08x %08x %08x %08x %08x\n",
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x08],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x09],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0a],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0b],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0c],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0d],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0e],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x0f]);
+ grub_dprintf ("ahci",
+ "last REG FIS %08x %08x %08x %08x %08x %08x %08x %08x\n",
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x10],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x11],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x12],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x13],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x14],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x15],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x16],
+ ((grub_uint32_t *) grub_dma_get_virt (dev->rfis))[0x17]);
+
+ if (!parms->write)
+ grub_memcpy (parms->buffer, (char *) grub_dma_get_virt (bufc), parms->size);
+ grub_dma_free (bufc);
+
+ return err;
+}
+
+static grub_err_t
+grub_ahci_readwrite (grub_ata_t disk,
+ struct grub_disk_ata_pass_through_parms *parms,
+ int spinup)
+{
+ return grub_ahci_readwrite_real (disk->data, parms, spinup, 0);
+}
+
+static grub_err_t
+grub_ahci_open (int id, int devnum, struct grub_ata *ata)
+{
+ struct grub_ahci_device *dev;
+
+ if (id != GRUB_SCSI_SUBSYSTEM_AHCI)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an AHCI device");
+
+ FOR_LIST_ELEMENTS(dev, grub_ahci_devices)
+ if (dev->num == devnum)
+ break;
+
+ if (! dev)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such AHCI device");
+
+ grub_dprintf ("ahci", "opening AHCI dev `ahci%d'\n", dev->num);
+
+ ata->data = dev;
+ ata->dma = 1;
+ ata->atapi = dev->atapi;
+ ata->maxbuffer = GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH;
+ ata->present = &dev->present;
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_ata_dev grub_ahci_dev =
+ {
+ .iterate = grub_ahci_iterate,
+ .open = grub_ahci_open,
+ .readwrite = grub_ahci_readwrite,
+ };
+
+
+
+static struct grub_preboot *fini_hnd;
+
+GRUB_MOD_INIT(ahci)
+{
+ grub_stop_disk_firmware ();
+
+ /* AHCI initialization. */
+ grub_ahci_initialize ();
+
+ /* AHCI devices are handled by scsi.mod. */
+ grub_ata_dev_register (&grub_ahci_dev);
+
+ fini_hnd = grub_loader_register_preboot_hook (grub_ahci_fini_hw,
+ grub_ahci_restore_hw,
+ GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI(ahci)
+{
+ grub_ahci_fini_hw (0);
+ grub_loader_unregister_preboot_hook (fini_hnd);
+
+ grub_ata_dev_unregister (&grub_ahci_dev);
+}
diff --git a/grub-core/disk/arc/arcdisk.c b/grub-core/disk/arc/arcdisk.c
new file mode 100644
index 0000000..c94039a
--- /dev/null
+++ b/grub-core/disk/arc/arcdisk.c
@@ -0,0 +1,325 @@
+/* ofdisk.c - Open Firmware disk access. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2004,2006,2007,2008,2009,2011 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/arc/arc.h>
+#include <grub/i18n.h>
+
+static grub_arc_fileno_t last_handle = 0;
+static char *last_path = NULL;
+static int handle_writable = 0;
+
+static int lnum = 0;
+
+struct arcdisk_hash_ent
+{
+ char *devpath;
+ int num;
+ struct arcdisk_hash_ent *next;
+};
+
+#define ARCDISK_HASH_SZ 8
+static struct arcdisk_hash_ent *arcdisk_hash[ARCDISK_HASH_SZ];
+
+static int
+arcdisk_hash_fn (const char *devpath)
+{
+ int hash = 0;
+ while (*devpath)
+ hash ^= *devpath++;
+ return (hash & (ARCDISK_HASH_SZ - 1));
+}
+
+static struct arcdisk_hash_ent *
+arcdisk_hash_find (const char *devpath)
+{
+ struct arcdisk_hash_ent *p = arcdisk_hash[arcdisk_hash_fn (devpath)];
+
+ while (p)
+ {
+ if (!grub_strcmp (p->devpath, devpath))
+ break;
+ p = p->next;
+ }
+ return p;
+}
+
+static struct arcdisk_hash_ent *
+arcdisk_hash_add (char *devpath)
+{
+ struct arcdisk_hash_ent *p;
+ struct arcdisk_hash_ent **head = &arcdisk_hash[arcdisk_hash_fn(devpath)];
+
+ p = grub_malloc (sizeof (*p));
+ if (!p)
+ return NULL;
+
+ p->devpath = devpath;
+ p->next = *head;
+ p->num = lnum++;
+ *head = p;
+ return p;
+}
+
+
+/* Context for grub_arcdisk_iterate. */
+struct grub_arcdisk_iterate_ctx
+{
+ grub_disk_dev_iterate_hook_t hook;
+ void *hook_data;
+};
+
+/* Helper for grub_arcdisk_iterate. */
+static int
+grub_arcdisk_iterate_iter (const char *name,
+ const struct grub_arc_component *comp, void *data)
+{
+ struct grub_arcdisk_iterate_ctx *ctx = data;
+
+ if (!(comp->type == GRUB_ARC_COMPONENT_TYPE_DISK
+ || comp->type == GRUB_ARC_COMPONENT_TYPE_FLOPPY
+ || comp->type == GRUB_ARC_COMPONENT_TYPE_TAPE))
+ return 0;
+ return ctx->hook (name, ctx->hook_data);
+}
+
+static int
+grub_arcdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_arcdisk_iterate_ctx ctx = { hook, hook_data };
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ return grub_arc_iterate_devs (grub_arcdisk_iterate_iter, &ctx, 1);
+}
+
+#ifdef GRUB_CPU_MIPSEL
+#define RAW_SUFFIX "partition(0)"
+#else
+#define RAW_SUFFIX "partition(10)"
+#endif
+
+static grub_err_t
+reopen (const char *name, int writable)
+{
+ grub_arc_fileno_t handle;
+
+ if (last_path && grub_strcmp (last_path, name) == 0
+ && (!writable || handle_writable))
+ {
+ grub_dprintf ("arcdisk", "using already opened %s\n", name);
+ return GRUB_ERR_NONE;
+ }
+ if (last_path)
+ {
+ GRUB_ARC_FIRMWARE_VECTOR->close (last_handle);
+ grub_free (last_path);
+ last_path = NULL;
+ last_handle = 0;
+ handle_writable = 0;
+ }
+ if (GRUB_ARC_FIRMWARE_VECTOR->open (name,
+ writable ? GRUB_ARC_FILE_ACCESS_OPEN_RW
+ : GRUB_ARC_FILE_ACCESS_OPEN_RO, &handle))
+ {
+ grub_dprintf ("arcdisk", "couldn't open %s\n", name);
+ return grub_error (GRUB_ERR_IO, "couldn't open %s", name);
+ }
+ handle_writable = writable;
+ last_path = grub_strdup (name);
+ if (!last_path)
+ return grub_errno;
+ last_handle = handle;
+ grub_dprintf ("arcdisk", "opened %s\n", name);
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_arcdisk_open (const char *name, grub_disk_t disk)
+{
+ char *fullname;
+ grub_err_t err;
+ grub_arc_err_t r;
+ struct grub_arc_fileinfo info;
+ struct arcdisk_hash_ent *hash;
+
+ if (grub_memcmp (name, "arc/", 4) != 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not arc device");
+ fullname = grub_arc_alt_name_to_norm (name, RAW_SUFFIX);
+ disk->data = fullname;
+ grub_dprintf ("arcdisk", "opening %s\n", fullname);
+
+ hash = arcdisk_hash_find (fullname);
+ if (!hash)
+ hash = arcdisk_hash_add (fullname);
+ if (!hash)
+ return grub_errno;
+
+ err = reopen (fullname, 0);
+ if (err)
+ return err;
+
+ r = GRUB_ARC_FIRMWARE_VECTOR->getfileinformation (last_handle, &info);
+ if (r)
+ {
+ grub_uint64_t res = 0;
+ int i;
+
+ grub_dprintf ("arcdisk", "couldn't retrieve size: %ld\n", r);
+ for (i = 40; i >= 9; i--)
+ {
+ grub_uint64_t pos = res | (1ULL << i);
+ char buf[512];
+ long unsigned count = 0;
+ grub_dprintf ("arcdisk",
+ "seek to 0x%" PRIxGRUB_UINT64_T "\n", pos);
+ if (GRUB_ARC_FIRMWARE_VECTOR->seek (last_handle, &pos, 0))
+ continue;
+ if (GRUB_ARC_FIRMWARE_VECTOR->read (last_handle, buf,
+ 0x200, &count))
+ continue;
+ if (count == 0)
+ continue;
+ res |= (1ULL << i);
+ }
+ grub_dprintf ("arcdisk",
+ "determined disk size 0x%" PRIxGRUB_UINT64_T "\n", res);
+ disk->total_sectors = (res + 0x200) >> 9;
+ }
+ else
+ disk->total_sectors = (info.end >> 9);
+
+ disk->id = hash->num;
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_arcdisk_close (grub_disk_t disk)
+{
+ grub_free (disk->data);
+}
+
+static grub_err_t
+grub_arcdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_err_t err;
+ grub_uint64_t pos = sector << 9;
+ unsigned long count;
+ grub_uint64_t totl = size << 9;
+ grub_arc_err_t r;
+
+ err = reopen (disk->data, 0);
+ if (err)
+ return err;
+ r = GRUB_ARC_FIRMWARE_VECTOR->seek (last_handle, &pos, 0);
+ if (r)
+ {
+ grub_dprintf ("arcdisk", "seek to 0x%" PRIxGRUB_UINT64_T " failed: %ld\n",
+ pos, r);
+ return grub_error (GRUB_ERR_IO, "couldn't seek");
+ }
+
+ while (totl)
+ {
+ if (GRUB_ARC_FIRMWARE_VECTOR->read (last_handle, buf,
+ totl, &count))
+ return grub_error (GRUB_ERR_READ_ERROR,
+ N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ totl -= count;
+ buf += count;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_arcdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ grub_err_t err;
+ grub_uint64_t pos = sector << 9;
+ unsigned long count;
+ grub_uint64_t totl = size << 9;
+ grub_arc_err_t r;
+
+ err = reopen (disk->data, 1);
+ if (err)
+ return err;
+ r = GRUB_ARC_FIRMWARE_VECTOR->seek (last_handle, &pos, 0);
+ if (r)
+ {
+ grub_dprintf ("arcdisk", "seek to 0x%" PRIxGRUB_UINT64_T " failed: %ld\n",
+ pos, r);
+ return grub_error (GRUB_ERR_IO, "couldn't seek");
+ }
+
+ while (totl)
+ {
+ if (GRUB_ARC_FIRMWARE_VECTOR->write (last_handle, buf,
+ totl, &count))
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx "
+ "to `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ totl -= count;
+ buf += count;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_disk_dev grub_arcdisk_dev =
+ {
+ .name = "arcdisk",
+ .id = GRUB_DISK_DEVICE_ARCDISK_ID,
+ .disk_iterate = grub_arcdisk_iterate,
+ .disk_open = grub_arcdisk_open,
+ .disk_close = grub_arcdisk_close,
+ .disk_read = grub_arcdisk_read,
+ .disk_write = grub_arcdisk_write,
+ .next = 0
+ };
+
+void
+grub_arcdisk_init (void)
+{
+ grub_disk_dev_register (&grub_arcdisk_dev);
+}
+
+void
+grub_arcdisk_fini (void)
+{
+ if (last_path)
+ {
+ GRUB_ARC_FIRMWARE_VECTOR->close (last_handle);
+ grub_free (last_path);
+ last_path = NULL;
+ last_handle = 0;
+ }
+
+ grub_disk_dev_unregister (&grub_arcdisk_dev);
+}
diff --git a/grub-core/disk/ata.c b/grub-core/disk/ata.c
new file mode 100644
index 0000000..3620a28
--- /dev/null
+++ b/grub-core/disk/ata.c
@@ -0,0 +1,685 @@
+/* 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/scsi.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_ata_dev_t grub_ata_dev_list;
+
+/* Byteorder has to be changed before strings can be read. */
+static void
+grub_ata_strncpy (grub_uint16_t *dst16, grub_uint16_t *src16, grub_size_t len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len / 2; i++)
+ *(dst16++) = grub_swap_bytes16 (*(src16++));
+ *dst16 = 0;
+}
+
+static void
+grub_ata_dumpinfo (struct grub_ata *dev, grub_uint16_t *info)
+{
+ grub_uint16_t text[21];
+
+ /* The device information was read, dump it for debugging. */
+ grub_ata_strncpy (text, info + 10, 20);
+ grub_dprintf ("ata", "Serial: %s\n", (char *) text);
+ grub_ata_strncpy (text, info + 23, 8);
+ grub_dprintf ("ata", "Firmware: %s\n", (char *) text);
+ grub_ata_strncpy (text, info + 27, 40);
+ grub_dprintf ("ata", "Model: %s\n", (char *) text);
+
+ if (! dev->atapi)
+ {
+ grub_dprintf ("ata", "Addressing: %d\n", dev->addr);
+ grub_dprintf ("ata", "Sectors: %lld\n", (unsigned long long) dev->size);
+ grub_dprintf ("ata", "Sector size: %u\n", 1U << dev->log_sector_size);
+ }
+}
+
+static grub_err_t
+grub_atapi_identify (struct grub_ata *dev)
+{
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_uint16_t *info;
+ grub_err_t err;
+
+ info = grub_malloc (GRUB_DISK_SECTOR_SIZE);
+ if (! info)
+ return grub_errno;
+
+ grub_memset (&parms, 0, sizeof (parms));
+ parms.taskfile.disk = 0xE0;
+ parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_PACKET_DEVICE;
+ parms.size = GRUB_DISK_SECTOR_SIZE;
+ parms.buffer = info;
+
+ err = dev->dev->readwrite (dev, &parms, *dev->present);
+ if (err)
+ {
+ *dev->present = 0;
+ return err;
+ }
+
+ if (parms.size != GRUB_DISK_SECTOR_SIZE)
+ {
+ *dev->present = 0;
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "device cannot be identified");
+ }
+
+ dev->atapi = 1;
+
+ grub_ata_dumpinfo (dev, info);
+
+ grub_free (info);
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_ata_identify (struct grub_ata *dev)
+{
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_uint64_t *info64;
+ grub_uint32_t *info32;
+ grub_uint16_t *info16;
+ grub_err_t err;
+
+ if (dev->atapi)
+ return grub_atapi_identify (dev);
+
+ info64 = grub_malloc (GRUB_DISK_SECTOR_SIZE);
+ info32 = (grub_uint32_t *) info64;
+ info16 = (grub_uint16_t *) info64;
+ if (! info16)
+ return grub_errno;
+
+ grub_memset (&parms, 0, sizeof (parms));
+ parms.buffer = info16;
+ parms.size = GRUB_DISK_SECTOR_SIZE;
+ parms.taskfile.disk = 0xE0;
+
+ parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_DEVICE;
+
+ err = dev->dev->readwrite (dev, &parms, *dev->present);
+
+ if (err || parms.size != GRUB_DISK_SECTOR_SIZE)
+ {
+ grub_uint8_t sts = parms.taskfile.status;
+ grub_free (info16);
+ grub_errno = GRUB_ERR_NONE;
+ if ((sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ
+ | GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_ERR
+ && (parms.taskfile.error & 0x04 /* ABRT */))
+ /* Device without ATA IDENTIFY, try ATAPI. */
+ return grub_atapi_identify (dev);
+
+ else if (sts == 0x00)
+ {
+ *dev->present = 0;
+ /* No device, return error but don't print message. */
+ return GRUB_ERR_UNKNOWN_DEVICE;
+ }
+ else
+ {
+ *dev->present = 0;
+ /* Other Error. */
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "device cannot be identified");
+ }
+ }
+
+ /* 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] & grub_cpu_to_le16_compile_time ((1 << 9)))
+ {
+ /* Check if LBA48 is supported. */
+ if (info16[83] & grub_cpu_to_le16_compile_time ((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 (info32[30]);
+ else
+ dev->size = grub_le_to_cpu64 (info64[25]);
+
+ if (info16[106] & grub_cpu_to_le16_compile_time ((1 << 12)))
+ {
+ grub_uint32_t secsize;
+ secsize = grub_le_to_cpu32 (grub_get_unaligned32 (&info16[117]));
+ if (secsize & (secsize - 1) || !secsize
+ || secsize > 1048576)
+ secsize = 256;
+ for (dev->log_sector_size = 0;
+ (1U << dev->log_sector_size) < secsize;
+ dev->log_sector_size++);
+ dev->log_sector_size++;
+ }
+ else
+ dev->log_sector_size = 9;
+
+ /* Read CHS information. */
+ dev->cylinders = grub_le_to_cpu16 (info16[1]);
+ dev->heads = grub_le_to_cpu16 (info16[3]);
+ dev->sectors_per_track = grub_le_to_cpu16 (info16[6]);
+
+ grub_ata_dumpinfo (dev, info16);
+
+ grub_free (info16);
+
+ return 0;
+}
+
+static grub_err_t
+grub_ata_setaddress (struct grub_ata *dev,
+ struct grub_disk_ata_pass_through_parms *parms,
+ grub_disk_addr_t sector,
+ grub_size_t size,
+ grub_ata_addressing_t addressing)
+{
+ switch (addressing)
+ {
+ case GRUB_ATA_CHS:
+ {
+ unsigned int cylinder;
+ unsigned int head;
+ unsigned int sect;
+
+ if (dev->sectors_per_track == 0
+ || dev->heads == 0)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ "sector %" PRIxGRUB_UINT64_T " cannot be "
+ "addressed using CHS addressing",
+ sector);
+
+ /* 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 %" PRIxGRUB_UINT64_T " cannot be "
+ "addressed using CHS addressing",
+ sector);
+
+ parms->taskfile.disk = 0xE0 | head;
+ parms->taskfile.sectnum = sect;
+ parms->taskfile.cyllsb = cylinder & 0xFF;
+ parms->taskfile.cylmsb = cylinder >> 8;
+
+ break;
+ }
+
+ case GRUB_ATA_LBA:
+ if (size == 256)
+ size = 0;
+ parms->taskfile.disk = 0xE0 | ((sector >> 24) & 0x0F);
+
+ parms->taskfile.sectors = size;
+ parms->taskfile.lba_low = sector & 0xFF;
+ parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
+ parms->taskfile.lba_high = (sector >> 16) & 0xFF;
+ break;
+
+ case GRUB_ATA_LBA48:
+ if (size == 65536)
+ size = 0;
+
+ parms->taskfile.disk = 0xE0;
+
+ /* Set "Previous". */
+ parms->taskfile.sectors = size & 0xFF;
+ parms->taskfile.lba_low = sector & 0xFF;
+ parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
+ parms->taskfile.lba_high = (sector >> 16) & 0xFF;
+
+ /* Set "Current". */
+ parms->taskfile.sectors48 = (size >> 8) & 0xFF;
+ parms->taskfile.lba48_low = (sector >> 24) & 0xFF;
+ parms->taskfile.lba48_mid = (sector >> 32) & 0xFF;
+ parms->taskfile.lba48_high = (sector >> 40) & 0xFF;
+
+ 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 *ata = disk->data;
+
+ grub_ata_addressing_t addressing = ata->addr;
+ grub_size_t batch;
+ int cmd, cmd_write;
+ grub_size_t nsectors = 0;
+
+ grub_dprintf("ata", "grub_ata_readwrite (size=%llu, rw=%d)\n",
+ (unsigned long long) size, rw);
+
+ if (addressing == GRUB_ATA_LBA48 && ((sector + size) >> 28) != 0)
+ {
+ if (ata->dma)
+ {
+ cmd = GRUB_ATA_CMD_READ_SECTORS_DMA_EXT;
+ cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_DMA_EXT;
+ }
+ else
+ {
+ 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;
+ if (ata->dma)
+ {
+ cmd = GRUB_ATA_CMD_READ_SECTORS_DMA;
+ cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_DMA;
+ }
+ else
+ {
+ cmd = GRUB_ATA_CMD_READ_SECTORS;
+ cmd_write = GRUB_ATA_CMD_WRITE_SECTORS;
+ }
+ }
+
+ if (addressing != GRUB_ATA_CHS)
+ batch = 256;
+ else
+ batch = 1;
+
+ while (nsectors < size)
+ {
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_err_t err;
+
+ 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);
+ grub_memset (&parms, 0, sizeof (parms));
+ grub_ata_setaddress (ata, &parms, sector, batch, addressing);
+ parms.taskfile.cmd = (! rw ? cmd : cmd_write);
+ parms.buffer = buf;
+ parms.size = batch << ata->log_sector_size;
+ parms.write = rw;
+ if (ata->dma)
+ parms.dma = 1;
+
+ err = ata->dev->readwrite (ata, &parms, 0);
+ if (err)
+ return err;
+ if (parms.size != batch << ata->log_sector_size)
+ return grub_error (GRUB_ERR_READ_ERROR, "incomplete read");
+ buf += batch << ata->log_sector_size;
+ sector += batch;
+ nsectors += batch;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+
+
+static inline void
+grub_ata_real_close (struct grub_ata *ata)
+{
+ if (ata->dev->close)
+ ata->dev->close (ata);
+}
+
+static struct grub_ata *
+grub_ata_real_open (int id, int bus)
+{
+ struct grub_ata *ata;
+ grub_ata_dev_t p;
+
+ ata = grub_zalloc (sizeof (*ata));
+ if (!ata)
+ return NULL;
+ for (p = grub_ata_dev_list; p; p = p->next)
+ {
+ grub_err_t err;
+ if (p->open (id, bus, ata))
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+ ata->dev = p;
+ /* Use the IDENTIFY DEVICE command to query the device. */
+ err = grub_ata_identify (ata);
+ if (err)
+ {
+ if (!grub_errno)
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device");
+ grub_free (ata);
+ return NULL;
+ }
+ return ata;
+ }
+ grub_free (ata);
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device");
+ return NULL;
+}
+
+/* Context for grub_ata_iterate. */
+struct grub_ata_iterate_ctx
+{
+ grub_disk_dev_iterate_hook_t hook;
+ void *hook_data;
+};
+
+/* Helper for grub_ata_iterate. */
+static int
+grub_ata_iterate_iter (int id, int bus, void *data)
+{
+ struct grub_ata_iterate_ctx *ctx = data;
+ struct grub_ata *ata;
+ int ret;
+ char devname[40];
+
+ ata = grub_ata_real_open (id, bus);
+
+ if (!ata)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ if (ata->atapi)
+ {
+ grub_ata_real_close (ata);
+ return 0;
+ }
+ grub_snprintf (devname, sizeof (devname),
+ "%s%d", grub_scsi_names[id], bus);
+ ret = ctx->hook (devname, ctx->hook_data);
+ grub_ata_real_close (ata);
+ return ret;
+}
+
+static int
+grub_ata_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_ata_iterate_ctx ctx = { hook, hook_data };
+ grub_ata_dev_t p;
+
+ for (p = grub_ata_dev_list; p; p = p->next)
+ if (p->iterate && p->iterate (grub_ata_iterate_iter, &ctx, pull))
+ return 1;
+ return 0;
+}
+
+static grub_err_t
+grub_ata_open (const char *name, grub_disk_t disk)
+{
+ unsigned id, bus;
+ struct grub_ata *ata;
+
+ for (id = 0; id < GRUB_SCSI_NUM_SUBSYSTEMS; id++)
+ if (grub_strncmp (grub_scsi_names[id], name,
+ grub_strlen (grub_scsi_names[id])) == 0
+ && grub_isdigit (name[grub_strlen (grub_scsi_names[id])]))
+ break;
+ if (id == GRUB_SCSI_NUM_SUBSYSTEMS)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
+ bus = grub_strtoul (name + grub_strlen (grub_scsi_names[id]), 0, 0);
+ ata = grub_ata_real_open (id, bus);
+ if (!ata)
+ return grub_errno;
+
+ if (ata->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
+
+ disk->total_sectors = ata->size;
+ disk->max_agglomerate = (ata->maxbuffer >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS));
+ if (disk->max_agglomerate > (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size)))
+ disk->max_agglomerate = (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size));
+
+ disk->log_sector_size = ata->log_sector_size;
+
+ disk->id = grub_make_scsi_id (id, bus, 0);
+
+ disk->data = ata;
+
+ return 0;
+}
+
+static void
+grub_ata_close (grub_disk_t disk)
+{
+ struct grub_ata *ata = disk->data;
+ grub_ata_real_close (ata);
+}
+
+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,
+ .disk_iterate = grub_ata_iterate,
+ .disk_open = grub_ata_open,
+ .disk_close = grub_ata_close,
+ .disk_read = grub_ata_read,
+ .disk_write = grub_ata_write,
+ .next = 0
+ };
+
+
+
+/* ATAPI code. */
+
+static grub_err_t
+grub_atapi_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf)
+{
+ struct grub_ata *dev = scsi->data;
+ struct grub_disk_ata_pass_through_parms parms;
+ grub_err_t err;
+
+ grub_dprintf("ata", "grub_atapi_read (size=%llu)\n", (unsigned long long) size);
+ grub_memset (&parms, 0, sizeof (parms));
+
+ parms.taskfile.disk = 0;
+ parms.taskfile.features = 0;
+ parms.taskfile.atapi_ireason = 0;
+ parms.taskfile.atapi_cnthigh = size >> 8;
+ parms.taskfile.atapi_cntlow = size & 0xff;
+ parms.taskfile.cmd = GRUB_ATA_CMD_PACKET;
+ parms.cmd = cmd;
+ parms.cmdsize = cmdsize;
+
+ parms.size = size;
+ parms.buffer = buf;
+
+ err = dev->dev->readwrite (dev, &parms, 0);
+ if (err)
+ return err;
+
+ if (parms.size != size)
+ return grub_error (GRUB_ERR_READ_ERROR, "incomplete ATAPI read");
+ 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)),
+ const 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 id, int bus, struct grub_scsi *scsi)
+{
+ struct grub_ata *ata;
+
+ ata = grub_ata_real_open (id, bus);
+ if (!ata)
+ return grub_errno;
+
+ if (! ata->atapi)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
+
+ scsi->data = ata;
+ scsi->luns = 1;
+
+ return GRUB_ERR_NONE;
+}
+
+/* Context for grub_atapi_iterate. */
+struct grub_atapi_iterate_ctx
+{
+ grub_scsi_dev_iterate_hook_t hook;
+ void *hook_data;
+};
+
+/* Helper for grub_atapi_iterate. */
+static int
+grub_atapi_iterate_iter (int id, int bus, void *data)
+{
+ struct grub_atapi_iterate_ctx *ctx = data;
+ struct grub_ata *ata;
+ int ret;
+
+ ata = grub_ata_real_open (id, bus);
+
+ if (!ata)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ if (!ata->atapi)
+ {
+ grub_ata_real_close (ata);
+ return 0;
+ }
+ ret = ctx->hook (id, bus, 1, ctx->hook_data);
+ grub_ata_real_close (ata);
+ return ret;
+}
+
+static int
+grub_atapi_iterate (grub_scsi_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_atapi_iterate_ctx ctx = { hook, hook_data };
+ grub_ata_dev_t p;
+
+ for (p = grub_ata_dev_list; p; p = p->next)
+ if (p->iterate && p->iterate (grub_atapi_iterate_iter, &ctx, pull))
+ return 1;
+ return 0;
+}
+
+static void
+grub_atapi_close (grub_scsi_t disk)
+{
+ struct grub_ata *ata = disk->data;
+ grub_ata_real_close (ata);
+}
+
+
+void
+grub_ata_dev_register (grub_ata_dev_t dev)
+{
+ dev->next = grub_ata_dev_list;
+ grub_ata_dev_list = dev;
+}
+
+void
+grub_ata_dev_unregister (grub_ata_dev_t dev)
+{
+ grub_ata_dev_t *p, q;
+
+ for (p = &grub_ata_dev_list, q = *p; q; p = &(q->next), q = q->next)
+ if (q == dev)
+ {
+ *p = q->next;
+ break;
+ }
+}
+
+static struct grub_scsi_dev grub_atapi_dev =
+ {
+ .iterate = grub_atapi_iterate,
+ .open = grub_atapi_open,
+ .close = grub_atapi_close,
+ .read = grub_atapi_read,
+ .write = grub_atapi_write,
+ .next = 0
+ };
+
+
+
+GRUB_MOD_INIT(ata)
+{
+ grub_disk_dev_register (&grub_atadisk_dev);
+
+ /* ATAPI devices are handled by scsi.mod. */
+ grub_scsi_dev_register (&grub_atapi_dev);
+}
+
+GRUB_MOD_FINI(ata)
+{
+ grub_scsi_dev_unregister (&grub_atapi_dev);
+ grub_disk_dev_unregister (&grub_atadisk_dev);
+}
diff --git a/grub-core/disk/cryptodisk.c b/grub-core/disk/cryptodisk.c
new file mode 100644
index 0000000..90f82b2
--- /dev/null
+++ b/grub-core/disk/cryptodisk.c
@@ -0,0 +1,1331 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2003,2007,2010,2011,2019 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/cryptodisk.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/dl.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+#include <grub/fs.h>
+#include <grub/file.h>
+#include <grub/procfs.h>
+#include <grub/partition.h>
+
+#ifdef GRUB_UTIL
+#include <grub/emu/hostdisk.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+grub_cryptodisk_dev_t grub_cryptodisk_list;
+
+static const struct grub_arg_option options[] =
+ {
+ {"uuid", 'u', 0, N_("Mount by UUID."), 0, 0},
+ /* TRANSLATORS: It's still restricted to cryptodisks only. */
+ {"all", 'a', 0, N_("Mount all."), 0, 0},
+ {"boot", 'b', 0, N_("Mount all volumes with `boot' flag set."), 0, 0},
+ {0, 0, 0, 0, 0, 0}
+ };
+
+/* Our irreducible polynom is x^128+x^7+x^2+x+1. Lowest byte of it is: */
+#define GF_POLYNOM 0x87
+static inline int GF_PER_SECTOR (const struct grub_cryptodisk *dev)
+{
+ return 1U << (dev->log_sector_size - GRUB_CRYPTODISK_GF_LOG_BYTES);
+}
+
+static grub_cryptodisk_t cryptodisk_list = NULL;
+static grub_uint8_t last_cryptodisk_id = 0;
+
+static void
+gf_mul_x (grub_uint8_t *g)
+{
+ int over = 0, over2 = 0;
+ unsigned j;
+
+ for (j = 0; j < GRUB_CRYPTODISK_GF_BYTES; j++)
+ {
+ over2 = !!(g[j] & 0x80);
+ g[j] <<= 1;
+ g[j] |= over;
+ over = over2;
+ }
+ if (over)
+ g[0] ^= GF_POLYNOM;
+}
+
+
+static void
+gf_mul_x_be (grub_uint8_t *g)
+{
+ int over = 0, over2 = 0;
+ int j;
+
+ for (j = (int) GRUB_CRYPTODISK_GF_BYTES - 1; j >= 0; j--)
+ {
+ over2 = !!(g[j] & 0x80);
+ g[j] <<= 1;
+ g[j] |= over;
+ over = over2;
+ }
+ if (over)
+ g[GRUB_CRYPTODISK_GF_BYTES - 1] ^= GF_POLYNOM;
+}
+
+static void
+gf_mul_be (grub_uint8_t *o, const grub_uint8_t *a, const grub_uint8_t *b)
+{
+ unsigned i;
+ grub_uint8_t t[GRUB_CRYPTODISK_GF_BYTES];
+ grub_memset (o, 0, GRUB_CRYPTODISK_GF_BYTES);
+ grub_memcpy (t, b, GRUB_CRYPTODISK_GF_BYTES);
+ for (i = 0; i < GRUB_CRYPTODISK_GF_SIZE; i++)
+ {
+ if (((a[GRUB_CRYPTODISK_GF_BYTES - i / GRUB_CHAR_BIT - 1] >> (i % GRUB_CHAR_BIT))) & 1)
+ grub_crypto_xor (o, o, t, GRUB_CRYPTODISK_GF_BYTES);
+ gf_mul_x_be (t);
+ }
+}
+
+static gcry_err_code_t
+grub_crypto_pcbc_decrypt (grub_crypto_cipher_handle_t cipher,
+ void *out, void *in, grub_size_t size,
+ void *iv)
+{
+ grub_uint8_t *inptr, *outptr, *end;
+ grub_uint8_t ivt[GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE];
+ grub_size_t blocksize;
+ if (!cipher->cipher->decrypt)
+ return GPG_ERR_NOT_SUPPORTED;
+ blocksize = cipher->cipher->blocksize;
+ if (blocksize == 0 || (((blocksize - 1) & blocksize) != 0)
+ || ((size & (blocksize - 1)) != 0))
+ return GPG_ERR_INV_ARG;
+ if (blocksize > GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE)
+ return GPG_ERR_INV_ARG;
+ end = (grub_uint8_t *) in + size;
+ for (inptr = in, outptr = out; inptr < end;
+ inptr += blocksize, outptr += blocksize)
+ {
+ grub_memcpy (ivt, inptr, blocksize);
+ cipher->cipher->decrypt (cipher->ctx, outptr, inptr);
+ grub_crypto_xor (outptr, outptr, iv, blocksize);
+ grub_crypto_xor (iv, ivt, outptr, blocksize);
+ }
+ return GPG_ERR_NO_ERROR;
+}
+
+static gcry_err_code_t
+grub_crypto_pcbc_encrypt (grub_crypto_cipher_handle_t cipher,
+ void *out, void *in, grub_size_t size,
+ void *iv)
+{
+ grub_uint8_t *inptr, *outptr, *end;
+ grub_uint8_t ivt[GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE];
+ grub_size_t blocksize;
+ if (!cipher->cipher->encrypt)
+ return GPG_ERR_NOT_SUPPORTED;
+ blocksize = cipher->cipher->blocksize;
+ if (blocksize > GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE)
+ return GPG_ERR_INV_ARG;
+ if (blocksize == 0 || (((blocksize - 1) & blocksize) != 0)
+ || ((size & (blocksize - 1)) != 0))
+ return GPG_ERR_INV_ARG;
+ end = (grub_uint8_t *) in + size;
+ for (inptr = in, outptr = out; inptr < end;
+ inptr += blocksize, outptr += blocksize)
+ {
+ grub_memcpy (ivt, inptr, blocksize);
+ grub_crypto_xor (outptr, outptr, iv, blocksize);
+ cipher->cipher->encrypt (cipher->ctx, outptr, inptr);
+ grub_crypto_xor (iv, ivt, outptr, blocksize);
+ }
+ return GPG_ERR_NO_ERROR;
+}
+
+struct lrw_sector
+{
+ grub_uint8_t low[GRUB_CRYPTODISK_GF_BYTES];
+ grub_uint8_t high[GRUB_CRYPTODISK_GF_BYTES];
+ grub_uint8_t low_byte, low_byte_c;
+};
+
+static void
+generate_lrw_sector (struct lrw_sector *sec,
+ const struct grub_cryptodisk *dev,
+ const grub_uint8_t *iv)
+{
+ grub_uint8_t idx[GRUB_CRYPTODISK_GF_BYTES];
+ grub_uint16_t c;
+ int j;
+ grub_memcpy (idx, iv, GRUB_CRYPTODISK_GF_BYTES);
+ sec->low_byte = (idx[GRUB_CRYPTODISK_GF_BYTES - 1]
+ & (GF_PER_SECTOR (dev) - 1));
+ sec->low_byte_c = (((GF_PER_SECTOR (dev) - 1) & ~sec->low_byte) + 1);
+ idx[GRUB_CRYPTODISK_GF_BYTES - 1] &= ~(GF_PER_SECTOR (dev) - 1);
+ gf_mul_be (sec->low, dev->lrw_key, idx);
+ if (!sec->low_byte)
+ return;
+
+ c = idx[GRUB_CRYPTODISK_GF_BYTES - 1] + GF_PER_SECTOR (dev);
+ if (c & 0x100)
+ {
+ for (j = GRUB_CRYPTODISK_GF_BYTES - 2; j >= 0; j--)
+ {
+ idx[j]++;
+ if (idx[j] != 0)
+ break;
+ }
+ }
+ idx[GRUB_CRYPTODISK_GF_BYTES - 1] = c;
+ gf_mul_be (sec->high, dev->lrw_key, idx);
+}
+
+static void __attribute__ ((unused))
+lrw_xor (const struct lrw_sector *sec,
+ const struct grub_cryptodisk *dev,
+ grub_uint8_t *b)
+{
+ unsigned i;
+
+ for (i = 0; i < sec->low_byte_c * GRUB_CRYPTODISK_GF_BYTES;
+ i += GRUB_CRYPTODISK_GF_BYTES)
+ grub_crypto_xor (b + i, b + i, sec->low, GRUB_CRYPTODISK_GF_BYTES);
+ grub_crypto_xor (b, b, dev->lrw_precalc + GRUB_CRYPTODISK_GF_BYTES * sec->low_byte,
+ sec->low_byte_c * GRUB_CRYPTODISK_GF_BYTES);
+ if (!sec->low_byte)
+ return;
+
+ for (i = sec->low_byte_c * GRUB_CRYPTODISK_GF_BYTES;
+ i < (1U << dev->log_sector_size); i += GRUB_CRYPTODISK_GF_BYTES)
+ grub_crypto_xor (b + i, b + i, sec->high, GRUB_CRYPTODISK_GF_BYTES);
+ grub_crypto_xor (b + sec->low_byte_c * GRUB_CRYPTODISK_GF_BYTES,
+ b + sec->low_byte_c * GRUB_CRYPTODISK_GF_BYTES,
+ dev->lrw_precalc, sec->low_byte * GRUB_CRYPTODISK_GF_BYTES);
+}
+
+static gcry_err_code_t
+grub_cryptodisk_endecrypt (struct grub_cryptodisk *dev,
+ grub_uint8_t * data, grub_size_t len,
+ grub_disk_addr_t sector, grub_size_t log_sector_size,
+ int do_encrypt)
+{
+ grub_size_t i;
+ gcry_err_code_t err;
+
+ if (dev->cipher->cipher->blocksize > GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE)
+ return GPG_ERR_INV_ARG;
+
+ /* The only mode without IV. */
+ if (dev->mode == GRUB_CRYPTODISK_MODE_ECB && !dev->rekey)
+ return (do_encrypt ? grub_crypto_ecb_encrypt (dev->cipher, data, data, len)
+ : grub_crypto_ecb_decrypt (dev->cipher, data, data, len));
+
+ for (i = 0; i < len; i += (1U << log_sector_size))
+ {
+ grub_size_t sz = ((dev->cipher->cipher->blocksize
+ + sizeof (grub_uint32_t) - 1)
+ / sizeof (grub_uint32_t));
+ grub_uint32_t iv[(GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE + 3) / 4];
+
+ if (dev->rekey)
+ {
+ grub_uint64_t zone = sector >> dev->rekey_shift;
+ if (zone != dev->last_rekey)
+ {
+ err = dev->rekey (dev, zone);
+ if (err)
+ return err;
+ dev->last_rekey = zone;
+ }
+ }
+
+ grub_memset (iv, 0, sizeof (iv));
+ switch (dev->mode_iv)
+ {
+ case GRUB_CRYPTODISK_MODE_IV_NULL:
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64_HASH:
+ {
+ grub_uint64_t tmp;
+ void *ctx;
+
+ ctx = grub_zalloc (dev->iv_hash->contextsize);
+ if (!ctx)
+ return GPG_ERR_OUT_OF_MEMORY;
+
+ tmp = grub_cpu_to_le64 (sector << log_sector_size);
+ dev->iv_hash->init (ctx);
+ dev->iv_hash->write (ctx, dev->iv_prefix, dev->iv_prefix_len);
+ dev->iv_hash->write (ctx, &tmp, sizeof (tmp));
+ dev->iv_hash->final (ctx);
+
+ grub_memcpy (iv, dev->iv_hash->read (ctx), sizeof (iv));
+ grub_free (ctx);
+ }
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_PLAIN64:
+ case GRUB_CRYPTODISK_MODE_IV_PLAIN:
+ /*
+ * The IV is a 32 or 64 bit value of the dm-crypt native sector
+ * number. If using 32 bit IV mode, zero out the most significant
+ * 32 bits.
+ */
+ {
+ grub_uint64_t iv64;
+
+ iv64 = grub_cpu_to_le64 (sector << (log_sector_size
+ - GRUB_CRYPTODISK_IV_LOG_SIZE));
+ grub_set_unaligned64 (iv, iv64);
+ if (dev->mode_iv == GRUB_CRYPTODISK_MODE_IV_PLAIN)
+ iv[1] = 0;
+ }
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64:
+ /* The IV is the 64 bit byte offset of the sector. */
+ iv[1] = grub_cpu_to_le32 (sector >> (GRUB_TYPE_BITS (iv[1])
+ - log_sector_size));
+ iv[0] = grub_cpu_to_le32 ((sector << log_sector_size)
+ & GRUB_TYPE_U_MAX (iv[0]));
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_BENBI:
+ {
+ grub_uint64_t num = (sector << dev->benbi_log) + 1;
+ iv[sz - 2] = grub_cpu_to_be32 (num >> GRUB_TYPE_BITS (iv[0]));
+ iv[sz - 1] = grub_cpu_to_be32 (num & GRUB_TYPE_U_MAX (iv[0]));
+ }
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_ESSIV:
+ iv[0] = grub_cpu_to_le32 (sector & GRUB_TYPE_U_MAX (iv[0]));
+ err = grub_crypto_ecb_encrypt (dev->essiv_cipher, iv, iv,
+ dev->cipher->cipher->blocksize);
+ if (err)
+ return err;
+ }
+
+ switch (dev->mode)
+ {
+ case GRUB_CRYPTODISK_MODE_CBC:
+ if (do_encrypt)
+ err = grub_crypto_cbc_encrypt (dev->cipher, data + i, data + i,
+ ((grub_size_t) 1 << log_sector_size), iv);
+ else
+ err = grub_crypto_cbc_decrypt (dev->cipher, data + i, data + i,
+ ((grub_size_t) 1 << log_sector_size), iv);
+ if (err)
+ return err;
+ break;
+
+ case GRUB_CRYPTODISK_MODE_PCBC:
+ if (do_encrypt)
+ err = grub_crypto_pcbc_encrypt (dev->cipher, data + i, data + i,
+ ((grub_size_t) 1 << log_sector_size), iv);
+ else
+ err = grub_crypto_pcbc_decrypt (dev->cipher, data + i, data + i,
+ ((grub_size_t) 1 << log_sector_size), iv);
+ if (err)
+ return err;
+ break;
+ case GRUB_CRYPTODISK_MODE_XTS:
+ {
+ unsigned j;
+ err = grub_crypto_ecb_encrypt (dev->secondary_cipher, iv, iv,
+ dev->cipher->cipher->blocksize);
+ if (err)
+ return err;
+
+ for (j = 0; j < (1U << log_sector_size);
+ j += dev->cipher->cipher->blocksize)
+ {
+ grub_crypto_xor (data + i + j, data + i + j, iv,
+ dev->cipher->cipher->blocksize);
+ if (do_encrypt)
+ err = grub_crypto_ecb_encrypt (dev->cipher, data + i + j,
+ data + i + j,
+ dev->cipher->cipher->blocksize);
+ else
+ err = grub_crypto_ecb_decrypt (dev->cipher, data + i + j,
+ data + i + j,
+ dev->cipher->cipher->blocksize);
+ if (err)
+ return err;
+ grub_crypto_xor (data + i + j, data + i + j, iv,
+ dev->cipher->cipher->blocksize);
+ gf_mul_x ((grub_uint8_t *) iv);
+ }
+ }
+ break;
+ case GRUB_CRYPTODISK_MODE_LRW:
+ {
+ struct lrw_sector sec;
+
+ generate_lrw_sector (&sec, dev, (grub_uint8_t *) iv);
+ lrw_xor (&sec, dev, data + i);
+
+ if (do_encrypt)
+ err = grub_crypto_ecb_encrypt (dev->cipher, data + i,
+ data + i,
+ (1U << log_sector_size));
+ else
+ err = grub_crypto_ecb_decrypt (dev->cipher, data + i,
+ data + i,
+ (1U << log_sector_size));
+ if (err)
+ return err;
+ lrw_xor (&sec, dev, data + i);
+ }
+ break;
+ case GRUB_CRYPTODISK_MODE_ECB:
+ if (do_encrypt)
+ err = grub_crypto_ecb_encrypt (dev->cipher, data + i, data + i,
+ (1U << log_sector_size));
+ else
+ err = grub_crypto_ecb_decrypt (dev->cipher, data + i, data + i,
+ (1U << log_sector_size));
+ if (err)
+ return err;
+ break;
+ default:
+ return GPG_ERR_NOT_IMPLEMENTED;
+ }
+ sector++;
+ }
+ return GPG_ERR_NO_ERROR;
+}
+
+gcry_err_code_t
+grub_cryptodisk_decrypt (struct grub_cryptodisk *dev,
+ grub_uint8_t * data, grub_size_t len,
+ grub_disk_addr_t sector, grub_size_t log_sector_size)
+{
+ return grub_cryptodisk_endecrypt (dev, data, len, sector, log_sector_size, 0);
+}
+
+grub_err_t
+grub_cryptodisk_setcipher (grub_cryptodisk_t crypt, const char *ciphername, const char *ciphermode)
+{
+ const char *cipheriv = NULL;
+ grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+ grub_crypto_cipher_handle_t essiv_cipher = NULL;
+ const gcry_md_spec_t *essiv_hash = NULL;
+ const struct gcry_cipher_spec *ciph;
+ grub_cryptodisk_mode_t mode;
+ grub_cryptodisk_mode_iv_t mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+ int benbi_log = 0;
+ grub_err_t ret = GRUB_ERR_NONE;
+
+ ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+ if (!ciph)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+ ciphername);
+ goto err;
+ }
+
+ /* Configure the cipher used for the bulk data. */
+ cipher = grub_crypto_cipher_open (ciph);
+ if (!cipher)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s could not be initialized",
+ ciphername);
+ goto err;
+ }
+
+ /* Configure the cipher mode. */
+ if (grub_strcmp (ciphermode, "ecb") == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_ECB;
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+ cipheriv = NULL;
+ }
+ else if (grub_strcmp (ciphermode, "plain") == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_CBC;
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+ cipheriv = NULL;
+ }
+ else if (grub_memcmp (ciphermode, "cbc-", sizeof ("cbc-") - 1) == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_CBC;
+ cipheriv = ciphermode + sizeof ("cbc-") - 1;
+ }
+ else if (grub_memcmp (ciphermode, "pcbc-", sizeof ("pcbc-") - 1) == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_PCBC;
+ cipheriv = ciphermode + sizeof ("pcbc-") - 1;
+ }
+ else if (grub_memcmp (ciphermode, "xts-", sizeof ("xts-") - 1) == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_XTS;
+ cipheriv = ciphermode + sizeof ("xts-") - 1;
+ secondary_cipher = grub_crypto_cipher_open (ciph);
+ if (!secondary_cipher)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ "Secondary cipher %s isn't available", ciphername);
+ goto err;
+ }
+ if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Unsupported XTS block size: %" PRIuGRUB_SIZE,
+ cipher->cipher->blocksize);
+ goto err;
+ }
+ if (secondary_cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Unsupported XTS block size: %" PRIuGRUB_SIZE,
+ secondary_cipher->cipher->blocksize);
+ goto err;
+ }
+ }
+ else if (grub_memcmp (ciphermode, "lrw-", sizeof ("lrw-") - 1) == 0)
+ {
+ mode = GRUB_CRYPTODISK_MODE_LRW;
+ cipheriv = ciphermode + sizeof ("lrw-") - 1;
+ if (cipher->cipher->blocksize != GRUB_CRYPTODISK_GF_BYTES)
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Unsupported LRW block size: %" PRIuGRUB_SIZE,
+ cipher->cipher->blocksize);
+ goto err;
+ }
+ }
+ else
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown cipher mode: %s",
+ ciphermode);
+ goto err;
+ }
+
+ if (cipheriv == NULL)
+ ;
+ else if (grub_memcmp (cipheriv, "plain64", sizeof ("plain64") - 1) == 0)
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN64;
+ else if (grub_memcmp (cipheriv, "plain", sizeof ("plain") - 1) == 0)
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_PLAIN;
+ else if (grub_memcmp (cipheriv, "benbi", sizeof ("benbi") - 1) == 0)
+ {
+ if (cipher->cipher->blocksize & (cipher->cipher->blocksize - 1)
+ || cipher->cipher->blocksize == 0)
+ grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Unsupported benbi blocksize: %" PRIuGRUB_SIZE,
+ cipher->cipher->blocksize);
+ /* FIXME should we return an error here? */
+ for (benbi_log = 0;
+ (cipher->cipher->blocksize << benbi_log) < GRUB_DISK_SECTOR_SIZE;
+ benbi_log++);
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_BENBI;
+ }
+ else if (grub_memcmp (cipheriv, "null", sizeof ("null") - 1) == 0)
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_NULL;
+ else if (grub_memcmp (cipheriv, "essiv:", sizeof ("essiv:") - 1) == 0)
+ {
+ const char *hash_str = cipheriv + 6;
+
+ mode_iv = GRUB_CRYPTODISK_MODE_IV_ESSIV;
+
+ /* Configure the hash and cipher used for ESSIV. */
+ essiv_hash = grub_crypto_lookup_md_by_name (hash_str);
+ if (!essiv_hash)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ "Couldn't load %s hash", hash_str);
+ goto err;
+ }
+ essiv_cipher = grub_crypto_cipher_open (ciph);
+ if (!essiv_cipher)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ "Couldn't load %s cipher", ciphername);
+ goto err;
+ }
+ }
+ else
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Unknown IV mode: %s",
+ cipheriv);
+ goto err;
+ }
+
+ crypt->cipher = cipher;
+ crypt->benbi_log = benbi_log;
+ crypt->mode = mode;
+ crypt->mode_iv = mode_iv;
+ crypt->secondary_cipher = secondary_cipher;
+ crypt->essiv_cipher = essiv_cipher;
+ crypt->essiv_hash = essiv_hash;
+
+err:
+ if (ret)
+ {
+ grub_crypto_cipher_close (cipher);
+ grub_crypto_cipher_close (secondary_cipher);
+ }
+ return ret;
+}
+
+gcry_err_code_t
+grub_cryptodisk_setkey (grub_cryptodisk_t dev, grub_uint8_t *key, grub_size_t keysize)
+{
+ gcry_err_code_t err;
+ int real_keysize;
+
+ real_keysize = keysize;
+ if (dev->mode == GRUB_CRYPTODISK_MODE_XTS)
+ real_keysize /= 2;
+ if (dev->mode == GRUB_CRYPTODISK_MODE_LRW)
+ real_keysize -= dev->cipher->cipher->blocksize;
+
+ /* Set the PBKDF2 output as the cipher key. */
+ err = grub_crypto_cipher_set_key (dev->cipher, key, real_keysize);
+ if (err)
+ return err;
+ grub_memcpy (dev->key, key, keysize);
+ dev->keysize = keysize;
+
+ /* Configure ESSIV if necessary. */
+ if (dev->mode_iv == GRUB_CRYPTODISK_MODE_IV_ESSIV)
+ {
+ grub_size_t essiv_keysize = dev->essiv_hash->mdlen;
+ grub_uint8_t hashed_key[GRUB_CRYPTO_MAX_MDLEN];
+ if (essiv_keysize > GRUB_CRYPTO_MAX_MDLEN)
+ return GPG_ERR_INV_ARG;
+
+ grub_crypto_hash (dev->essiv_hash, hashed_key, key, keysize);
+ err = grub_crypto_cipher_set_key (dev->essiv_cipher,
+ hashed_key, essiv_keysize);
+ if (err)
+ return err;
+ }
+ if (dev->mode == GRUB_CRYPTODISK_MODE_XTS)
+ {
+ err = grub_crypto_cipher_set_key (dev->secondary_cipher,
+ key + real_keysize,
+ keysize / 2);
+ if (err)
+ return err;
+ }
+
+ if (dev->mode == GRUB_CRYPTODISK_MODE_LRW)
+ {
+ unsigned i;
+ grub_uint8_t idx[GRUB_CRYPTODISK_GF_BYTES];
+
+ grub_free (dev->lrw_precalc);
+ grub_memcpy (dev->lrw_key, key + real_keysize,
+ dev->cipher->cipher->blocksize);
+ dev->lrw_precalc = grub_malloc ((1U << dev->log_sector_size));
+ if (!dev->lrw_precalc)
+ return GPG_ERR_OUT_OF_MEMORY;
+ grub_memset (idx, 0, GRUB_CRYPTODISK_GF_BYTES);
+ for (i = 0; i < (1U << dev->log_sector_size);
+ i += GRUB_CRYPTODISK_GF_BYTES)
+ {
+ idx[GRUB_CRYPTODISK_GF_BYTES - 1] = i / GRUB_CRYPTODISK_GF_BYTES;
+ gf_mul_be (dev->lrw_precalc + i, idx, dev->lrw_key);
+ }
+ }
+ return GPG_ERR_NO_ERROR;
+}
+
+static int
+grub_cryptodisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ grub_cryptodisk_t i;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ for (i = cryptodisk_list; i != NULL; i = i->next)
+ {
+ char buf[30];
+ grub_snprintf (buf, sizeof (buf), "crypto%lu", i->id);
+ if (hook (buf, hook_data))
+ return 1;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_cryptodisk_open (const char *name, grub_disk_t disk)
+{
+ grub_cryptodisk_t dev;
+
+ if (grub_memcmp (name, "crypto", sizeof ("crypto") - 1) != 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "No such device");
+
+ if (grub_memcmp (name, "cryptouuid/", sizeof ("cryptouuid/") - 1) == 0)
+ {
+ for (dev = cryptodisk_list; dev != NULL; dev = dev->next)
+ if (grub_strcasecmp (name + sizeof ("cryptouuid/") - 1, dev->uuid) == 0)
+ break;
+ }
+ else
+ {
+ unsigned long id = grub_strtoul (name + sizeof ("crypto") - 1, 0, 0);
+ if (grub_errno)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "No such device");
+ /* Search for requested device in the list of CRYPTODISK devices. */
+ for (dev = cryptodisk_list; dev != NULL; dev = dev->next)
+ if (dev->id == id)
+ break;
+ }
+ if (!dev)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "No such device");
+
+ disk->log_sector_size = dev->log_sector_size;
+
+#ifdef GRUB_UTIL
+ if (dev->cheat)
+ {
+ if (!GRUB_UTIL_FD_IS_VALID (dev->cheat_fd))
+ dev->cheat_fd = grub_util_fd_open (dev->cheat, GRUB_UTIL_FD_O_RDONLY);
+ if (!GRUB_UTIL_FD_IS_VALID (dev->cheat_fd))
+ return grub_error (GRUB_ERR_IO, N_("cannot open `%s': %s"),
+ dev->cheat, grub_util_fd_strerror ());
+ }
+#endif
+
+ if (!dev->source_disk)
+ {
+ grub_dprintf ("cryptodisk", "Opening device %s\n", name);
+ /* Try to open the source disk and populate the requested disk. */
+ dev->source_disk = grub_disk_open (dev->source);
+ if (!dev->source_disk)
+ return grub_errno;
+ }
+
+ disk->data = dev;
+ disk->total_sectors = dev->total_sectors;
+ disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE;
+ disk->id = dev->id;
+ dev->ref++;
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_cryptodisk_close (grub_disk_t disk)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+ grub_dprintf ("cryptodisk", "Closing disk\n");
+
+ dev->ref--;
+
+ if (dev->ref != 0)
+ return;
+#ifdef GRUB_UTIL
+ if (dev->cheat)
+ {
+ grub_util_fd_close (dev->cheat_fd);
+ dev->cheat_fd = GRUB_UTIL_FD_INVALID;
+ }
+#endif
+ grub_disk_close (dev->source_disk);
+ dev->source_disk = NULL;
+}
+
+static grub_err_t
+grub_cryptodisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+ grub_err_t err;
+ gcry_err_code_t gcry_err;
+
+#ifdef GRUB_UTIL
+ if (dev->cheat)
+ {
+ int r;
+ r = grub_util_fd_seek (dev->cheat_fd, sector << disk->log_sector_size);
+ if (r)
+ return grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot seek `%s': %s"),
+ dev->cheat, grub_util_fd_strerror ());
+ if (grub_util_fd_read (dev->cheat_fd, buf, size << disk->log_sector_size)
+ != (ssize_t) (size << disk->log_sector_size))
+ return grub_error (GRUB_ERR_READ_ERROR, N_("cannot read `%s': %s"),
+ dev->cheat, grub_util_fd_strerror ());
+ return GRUB_ERR_NONE;
+ }
+#endif
+
+ grub_dprintf ("cryptodisk",
+ "Reading %" PRIuGRUB_SIZE " sectors from sector 0x%"
+ PRIxGRUB_UINT64_T " with offset of %" PRIuGRUB_UINT64_T "\n",
+ size, sector, dev->offset_sectors);
+
+ err = grub_disk_read (dev->source_disk,
+ grub_disk_from_native_sector (disk, sector + dev->offset_sectors),
+ 0, size << disk->log_sector_size, buf);
+ if (err)
+ {
+ grub_dprintf ("cryptodisk", "grub_disk_read failed with error %d\n", err);
+ return err;
+ }
+ gcry_err = grub_cryptodisk_endecrypt (dev, (grub_uint8_t *) buf,
+ size << disk->log_sector_size,
+ sector, dev->log_sector_size, 0);
+ return grub_crypto_gcry_error (gcry_err);
+}
+
+static grub_err_t
+grub_cryptodisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+ gcry_err_code_t gcry_err;
+ char *tmp;
+ grub_err_t err;
+
+#ifdef GRUB_UTIL
+ if (dev->cheat)
+ {
+ int r;
+ r = grub_util_fd_seek (dev->cheat_fd, sector << disk->log_sector_size);
+ if (r)
+ return grub_error (GRUB_ERR_BAD_DEVICE, N_("cannot seek `%s': %s"),
+ dev->cheat, grub_util_fd_strerror ());
+ if (grub_util_fd_write (dev->cheat_fd, buf, size << disk->log_sector_size)
+ != (ssize_t) (size << disk->log_sector_size))
+ return grub_error (GRUB_ERR_READ_ERROR, N_("cannot read `%s': %s"),
+ dev->cheat, grub_util_fd_strerror ());
+ return GRUB_ERR_NONE;
+ }
+#endif
+
+ tmp = grub_malloc (size << disk->log_sector_size);
+ if (!tmp)
+ return grub_errno;
+ grub_memcpy (tmp, buf, size << disk->log_sector_size);
+
+ grub_dprintf ("cryptodisk",
+ "Writing %" PRIuGRUB_SIZE " sectors to sector 0x%"
+ PRIxGRUB_UINT64_T " with offset of %" PRIuGRUB_UINT64_T "\n",
+ size, sector, dev->offset_sectors);
+
+ gcry_err = grub_cryptodisk_endecrypt (dev, (grub_uint8_t *) tmp,
+ size << disk->log_sector_size,
+ sector, disk->log_sector_size, 1);
+ if (gcry_err)
+ {
+ grub_free (tmp);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ /* Since ->write was called so disk.mod is loaded but be paranoid */
+ sector = sector + dev->offset_sectors;
+ if (grub_disk_write_weak)
+ err = grub_disk_write_weak (dev->source_disk,
+ grub_disk_from_native_sector (disk, sector),
+ 0, size << disk->log_sector_size, tmp);
+ else
+ err = grub_error (GRUB_ERR_BUG, "disk.mod not loaded");
+ grub_free (tmp);
+ return err;
+}
+
+#ifdef GRUB_UTIL
+static grub_disk_memberlist_t
+grub_cryptodisk_memberlist (grub_disk_t disk)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+ grub_disk_memberlist_t list = NULL;
+
+ list = grub_malloc (sizeof (*list));
+ if (list)
+ {
+ list->disk = dev->source_disk;
+ list->next = NULL;
+ }
+
+ return list;
+}
+#endif
+
+static void
+cryptodisk_cleanup (void)
+{
+#if 0
+ grub_cryptodisk_t dev = cryptodisk_list;
+ grub_cryptodisk_t tmp;
+
+ while (dev != NULL)
+ {
+ grub_free (dev->source);
+ grub_free (dev->cipher);
+ grub_free (dev->secondary_cipher);
+ grub_free (dev->essiv_cipher);
+ tmp = dev->next;
+ grub_free (dev);
+ dev = tmp;
+ }
+#endif
+}
+
+grub_err_t
+grub_cryptodisk_insert (grub_cryptodisk_t newdev, const char *name,
+ grub_disk_t source)
+{
+ newdev->source = grub_strdup (name);
+ if (!newdev->source)
+ {
+ grub_free (newdev);
+ return grub_errno;
+ }
+
+ newdev->id = last_cryptodisk_id++;
+ newdev->source_id = source->id;
+ newdev->source_dev_id = source->dev->id;
+ newdev->partition_start = grub_partition_get_start (source->partition);
+ newdev->next = cryptodisk_list;
+ cryptodisk_list = newdev;
+
+ return GRUB_ERR_NONE;
+}
+
+grub_cryptodisk_t
+grub_cryptodisk_get_by_uuid (const char *uuid)
+{
+ grub_cryptodisk_t dev;
+ for (dev = cryptodisk_list; dev != NULL; dev = dev->next)
+ if (grub_strcasecmp (dev->uuid, uuid) == 0)
+ return dev;
+ return NULL;
+}
+
+grub_cryptodisk_t
+grub_cryptodisk_get_by_source_disk (grub_disk_t disk)
+{
+ grub_cryptodisk_t dev;
+ for (dev = cryptodisk_list; dev != NULL; dev = dev->next)
+ if (dev->source_id == disk->id && dev->source_dev_id == disk->dev->id)
+ if ((disk->partition && grub_partition_get_start (disk->partition) == dev->partition_start) ||
+ (!disk->partition && dev->partition_start == 0))
+ return dev;
+ return NULL;
+}
+
+#ifdef GRUB_UTIL
+grub_err_t
+grub_cryptodisk_cheat_insert (grub_cryptodisk_t newdev, const char *name,
+ grub_disk_t source, const char *cheat)
+{
+ newdev->cheat = grub_strdup (cheat);
+ newdev->source = grub_strdup (name);
+ if (!newdev->source || !newdev->cheat)
+ {
+ grub_free (newdev->source);
+ grub_free (newdev->cheat);
+ return grub_errno;
+ }
+
+ newdev->cheat_fd = GRUB_UTIL_FD_INVALID;
+ newdev->source_id = source->id;
+ newdev->source_dev_id = source->dev->id;
+ newdev->partition_start = grub_partition_get_start (source->partition);
+ newdev->id = last_cryptodisk_id++;
+ newdev->next = cryptodisk_list;
+ cryptodisk_list = newdev;
+
+ return GRUB_ERR_NONE;
+}
+
+void
+grub_util_cryptodisk_get_abstraction (grub_disk_t disk,
+ void (*cb) (const char *val, void *data),
+ void *data)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+
+ cb ("cryptodisk", data);
+ cb (dev->modname, data);
+
+ if (dev->cipher)
+ cb (dev->cipher->cipher->modname, data);
+ if (dev->secondary_cipher)
+ cb (dev->secondary_cipher->cipher->modname, data);
+ if (dev->essiv_cipher)
+ cb (dev->essiv_cipher->cipher->modname, data);
+ if (dev->hash)
+ cb (dev->hash->modname, data);
+ if (dev->essiv_hash)
+ cb (dev->essiv_hash->modname, data);
+ if (dev->iv_hash)
+ cb (dev->iv_hash->modname, data);
+}
+
+const char *
+grub_util_cryptodisk_get_uuid (grub_disk_t disk)
+{
+ grub_cryptodisk_t dev = (grub_cryptodisk_t) disk->data;
+ return dev->uuid;
+}
+
+#endif
+
+static int check_boot, have_it;
+static char *search_uuid;
+
+static void
+cryptodisk_close (grub_cryptodisk_t dev)
+{
+ grub_crypto_cipher_close (dev->cipher);
+ grub_crypto_cipher_close (dev->secondary_cipher);
+ grub_crypto_cipher_close (dev->essiv_cipher);
+ grub_free (dev);
+}
+
+static grub_err_t
+grub_cryptodisk_scan_device_real (const char *name, grub_disk_t source)
+{
+ grub_err_t err;
+ grub_cryptodisk_t dev;
+ grub_cryptodisk_dev_t cr;
+
+ dev = grub_cryptodisk_get_by_source_disk (source);
+
+ if (dev)
+ return GRUB_ERR_NONE;
+
+ FOR_CRYPTODISK_DEVS (cr)
+ {
+ dev = cr->scan (source, search_uuid, check_boot);
+ if (grub_errno)
+ return grub_errno;
+ if (!dev)
+ continue;
+
+ err = cr->recover_key (source, dev);
+ if (err)
+ {
+ cryptodisk_close (dev);
+ return err;
+ }
+
+ grub_cryptodisk_insert (dev, name, source);
+
+ have_it = 1;
+
+ return GRUB_ERR_NONE;
+ }
+ return GRUB_ERR_NONE;
+}
+
+#ifdef GRUB_UTIL
+#include <grub/util/misc.h>
+grub_err_t
+grub_cryptodisk_cheat_mount (const char *sourcedev, const char *cheat)
+{
+ grub_err_t err;
+ grub_cryptodisk_t dev;
+ grub_cryptodisk_dev_t cr;
+ grub_disk_t source;
+
+ /* Try to open disk. */
+ source = grub_disk_open (sourcedev);
+ if (!source)
+ return grub_errno;
+
+ dev = grub_cryptodisk_get_by_source_disk (source);
+
+ if (dev)
+ {
+ grub_disk_close (source);
+ return GRUB_ERR_NONE;
+ }
+
+ FOR_CRYPTODISK_DEVS (cr)
+ {
+ dev = cr->scan (source, search_uuid, check_boot);
+ if (grub_errno)
+ return grub_errno;
+ if (!dev)
+ continue;
+
+ grub_util_info ("cheatmounted %s (%s) at %s", sourcedev, dev->modname,
+ cheat);
+ err = grub_cryptodisk_cheat_insert (dev, sourcedev, source, cheat);
+ grub_disk_close (source);
+ if (err)
+ grub_free (dev);
+
+ return GRUB_ERR_NONE;
+ }
+
+ grub_disk_close (source);
+
+ return GRUB_ERR_NONE;
+}
+#endif
+
+static int
+grub_cryptodisk_scan_device (const char *name,
+ void *data __attribute__ ((unused)))
+{
+ grub_err_t err;
+ grub_disk_t source;
+
+ /* Try to open disk. */
+ source = grub_disk_open (name);
+ if (!source)
+ {
+ grub_print_error ();
+ return 0;
+ }
+
+ err = grub_cryptodisk_scan_device_real (name, source);
+
+ grub_disk_close (source);
+
+ if (err)
+ grub_print_error ();
+ return have_it && search_uuid ? 1 : 0;
+}
+
+static grub_err_t
+grub_cmd_cryptomount (grub_extcmd_context_t ctxt, int argc, char **args)
+{
+ struct grub_arg_list *state = ctxt->state;
+
+ if (argc < 1 && !state[1].set && !state[2].set)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required");
+
+ have_it = 0;
+ if (state[0].set)
+ {
+ grub_cryptodisk_t dev;
+
+ dev = grub_cryptodisk_get_by_uuid (args[0]);
+ if (dev)
+ {
+ grub_dprintf ("cryptodisk",
+ "already mounted as crypto%lu\n", dev->id);
+ return GRUB_ERR_NONE;
+ }
+
+ check_boot = state[2].set;
+ search_uuid = args[0];
+ grub_device_iterate (&grub_cryptodisk_scan_device, NULL);
+ search_uuid = NULL;
+
+ if (!have_it)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "no such cryptodisk found");
+ return GRUB_ERR_NONE;
+ }
+ else if (state[1].set || (argc == 0 && state[2].set))
+ {
+ search_uuid = NULL;
+ check_boot = state[2].set;
+ grub_device_iterate (&grub_cryptodisk_scan_device, NULL);
+ search_uuid = NULL;
+ return GRUB_ERR_NONE;
+ }
+ else
+ {
+ grub_err_t err;
+ grub_disk_t disk;
+ grub_cryptodisk_t dev;
+ char *diskname;
+ char *disklast = NULL;
+ grub_size_t len;
+
+ search_uuid = NULL;
+ check_boot = state[2].set;
+ diskname = args[0];
+ len = grub_strlen (diskname);
+ if (len && diskname[0] == '(' && diskname[len - 1] == ')')
+ {
+ disklast = &diskname[len - 1];
+ *disklast = '\0';
+ diskname++;
+ }
+
+ disk = grub_disk_open (diskname);
+ if (!disk)
+ {
+ if (disklast)
+ *disklast = ')';
+ return grub_errno;
+ }
+
+ dev = grub_cryptodisk_get_by_source_disk (disk);
+ if (dev)
+ {
+ grub_dprintf ("cryptodisk", "already mounted as crypto%lu\n", dev->id);
+ grub_disk_close (disk);
+ if (disklast)
+ *disklast = ')';
+ return GRUB_ERR_NONE;
+ }
+
+ err = grub_cryptodisk_scan_device_real (diskname, disk);
+
+ grub_disk_close (disk);
+ if (disklast)
+ *disklast = ')';
+
+ return err;
+ }
+}
+
+static struct grub_disk_dev grub_cryptodisk_dev = {
+ .name = "cryptodisk",
+ .id = GRUB_DISK_DEVICE_CRYPTODISK_ID,
+ .disk_iterate = grub_cryptodisk_iterate,
+ .disk_open = grub_cryptodisk_open,
+ .disk_close = grub_cryptodisk_close,
+ .disk_read = grub_cryptodisk_read,
+ .disk_write = grub_cryptodisk_write,
+#ifdef GRUB_UTIL
+ .disk_memberlist = grub_cryptodisk_memberlist,
+#endif
+ .next = 0
+};
+
+static char
+hex (grub_uint8_t val)
+{
+ if (val < 10)
+ return '0' + val;
+ return 'a' + val - 10;
+}
+
+/* Open a file named NAME and initialize FILE. */
+static char *
+luks_script_get (grub_size_t *sz)
+{
+ grub_cryptodisk_t i;
+ grub_size_t size = 0;
+ char *ptr, *ret;
+
+ *sz = 0;
+
+ for (i = cryptodisk_list; i != NULL; i = i->next)
+ if (grub_strcmp (i->modname, "luks") == 0)
+ {
+ size += sizeof ("luks_mount ");
+ size += grub_strlen (i->uuid);
+ size += grub_strlen (i->cipher->cipher->name);
+ size += 54;
+ if (i->essiv_hash)
+ size += grub_strlen (i->essiv_hash->name);
+ size += i->keysize * 2;
+ }
+
+ ret = grub_malloc (size + 1);
+ if (!ret)
+ return 0;
+
+ ptr = ret;
+
+ for (i = cryptodisk_list; i != NULL; i = i->next)
+ if (grub_strcmp (i->modname, "luks") == 0)
+ {
+ unsigned j;
+ const char *iptr;
+ ptr = grub_stpcpy (ptr, "luks_mount ");
+ ptr = grub_stpcpy (ptr, i->uuid);
+ *ptr++ = ' ';
+ grub_snprintf (ptr, 21, "%" PRIuGRUB_UINT64_T " ", i->offset_sectors);
+ while (*ptr)
+ ptr++;
+ for (iptr = i->cipher->cipher->name; *iptr; iptr++)
+ *ptr++ = grub_tolower (*iptr);
+ switch (i->mode)
+ {
+ case GRUB_CRYPTODISK_MODE_ECB:
+ ptr = grub_stpcpy (ptr, "-ecb");
+ break;
+ case GRUB_CRYPTODISK_MODE_CBC:
+ ptr = grub_stpcpy (ptr, "-cbc");
+ break;
+ case GRUB_CRYPTODISK_MODE_PCBC:
+ ptr = grub_stpcpy (ptr, "-pcbc");
+ break;
+ case GRUB_CRYPTODISK_MODE_XTS:
+ ptr = grub_stpcpy (ptr, "-xts");
+ break;
+ case GRUB_CRYPTODISK_MODE_LRW:
+ ptr = grub_stpcpy (ptr, "-lrw");
+ break;
+ }
+
+ switch (i->mode_iv)
+ {
+ case GRUB_CRYPTODISK_MODE_IV_NULL:
+ ptr = grub_stpcpy (ptr, "-null");
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_PLAIN:
+ ptr = grub_stpcpy (ptr, "-plain");
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_PLAIN64:
+ ptr = grub_stpcpy (ptr, "-plain64");
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_BENBI:
+ ptr = grub_stpcpy (ptr, "-benbi");
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_ESSIV:
+ ptr = grub_stpcpy (ptr, "-essiv:");
+ ptr = grub_stpcpy (ptr, i->essiv_hash->name);
+ break;
+ case GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64:
+ case GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64_HASH:
+ break;
+ }
+ *ptr++ = ' ';
+ for (j = 0; j < i->keysize; j++)
+ {
+ *ptr++ = hex (i->key[j] >> 4);
+ *ptr++ = hex (i->key[j] & 0xf);
+ }
+ *ptr++ = '\n';
+ }
+ *ptr = '\0';
+ *sz = ptr - ret;
+ return ret;
+}
+
+struct grub_procfs_entry luks_script =
+{
+ .name = "luks_script",
+ .get_contents = luks_script_get
+};
+
+static grub_extcmd_t cmd;
+
+GRUB_MOD_INIT (cryptodisk)
+{
+ grub_disk_dev_register (&grub_cryptodisk_dev);
+ cmd = grub_register_extcmd ("cryptomount", grub_cmd_cryptomount, 0,
+ N_("SOURCE|-u UUID|-a|-b"),
+ N_("Mount a crypto device."), options);
+ grub_procfs_register ("luks_script", &luks_script);
+}
+
+GRUB_MOD_FINI (cryptodisk)
+{
+ grub_disk_dev_unregister (&grub_cryptodisk_dev);
+ cryptodisk_cleanup ();
+ grub_unregister_extcmd (cmd);
+ grub_procfs_unregister (&luks_script);
+}
diff --git a/grub-core/disk/diskfilter.c b/grub-core/disk/diskfilter.c
new file mode 100644
index 0000000..0320115
--- /dev/null
+++ b/grub-core/disk/diskfilter.c
@@ -0,0 +1,1351 @@
+/* diskfilter.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/diskfilter.h>
+#include <grub/partition.h>
+#ifdef GRUB_UTIL
+#include <grub/i18n.h>
+#include <grub/util/misc.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Linked list of DISKFILTER arrays. */
+static struct grub_diskfilter_vg *array_list;
+grub_raid5_recover_func_t grub_raid5_recover_func;
+grub_raid6_recover_func_t grub_raid6_recover_func;
+grub_diskfilter_t grub_diskfilter_list;
+static int inscnt = 0;
+static int lv_num = 0;
+
+static struct grub_diskfilter_lv *
+find_lv (const char *name);
+static int is_lv_readable (struct grub_diskfilter_lv *lv, int easily);
+
+
+
+static grub_err_t
+is_node_readable (const struct grub_diskfilter_node *node, int easily)
+{
+ /* Check whether we actually know the physical volume we want to
+ read from. */
+ if (node->pv)
+ return !!(node->pv->disk);
+ if (node->lv)
+ return is_lv_readable (node->lv, easily);
+ return 0;
+}
+
+static int
+is_lv_readable (struct grub_diskfilter_lv *lv, int easily)
+{
+ unsigned i, j;
+ if (!lv)
+ return 0;
+ for (i = 0; i < lv->segment_count; i++)
+ {
+ int need = lv->segments[i].node_count, have = 0;
+ switch (lv->segments[i].type)
+ {
+ case GRUB_DISKFILTER_RAID6:
+ if (!easily)
+ need--;
+ /* Fallthrough. */
+ case GRUB_DISKFILTER_RAID4:
+ case GRUB_DISKFILTER_RAID5:
+ if (!easily)
+ need--;
+ /* Fallthrough. */
+ case GRUB_DISKFILTER_STRIPED:
+ break;
+
+ case GRUB_DISKFILTER_MIRROR:
+ need = 1;
+ break;
+
+ case GRUB_DISKFILTER_RAID10:
+ {
+ unsigned int n;
+ n = lv->segments[i].layout & 0xFF;
+ if (n == 1)
+ n = (lv->segments[i].layout >> 8) & 0xFF;
+ need = lv->segments[i].node_count - n + 1;
+ }
+ break;
+ }
+ for (j = 0; j < lv->segments[i].node_count; j++)
+ {
+ if (is_node_readable (lv->segments[i].nodes + j, easily))
+ have++;
+ if (have >= need)
+ break;
+ }
+ if (have < need)
+ return 0;
+ }
+
+ return 1;
+}
+
+static grub_err_t
+insert_array (grub_disk_t disk, const struct grub_diskfilter_pv_id *id,
+ struct grub_diskfilter_vg *array,
+ grub_disk_addr_t start_sector,
+ grub_diskfilter_t diskfilter __attribute__ ((unused)));
+
+static int
+is_valid_diskfilter_name (const char *name)
+{
+ return (grub_memcmp (name, "md", sizeof ("md") - 1) == 0
+ || grub_memcmp (name, "lvm/", sizeof ("lvm/") - 1) == 0
+ || grub_memcmp (name, "lvmid/", sizeof ("lvmid/") - 1) == 0
+ || grub_memcmp (name, "ldm/", sizeof ("ldm/") - 1) == 0);
+}
+
+/* Helper for scan_disk. */
+static int
+scan_disk_partition_iter (grub_disk_t disk, grub_partition_t p, void *data)
+{
+ const char *name = data;
+ struct grub_diskfilter_vg *arr;
+ grub_disk_addr_t start_sector;
+ struct grub_diskfilter_pv_id id;
+ grub_diskfilter_t diskfilter;
+
+ grub_dprintf ("diskfilter", "Scanning for DISKFILTER devices on disk %s\n",
+ name);
+#ifdef GRUB_UTIL
+ grub_util_info ("Scanning for DISKFILTER devices on disk %s", name);
+#endif
+
+ disk->partition = p;
+
+ for (arr = array_list; arr != NULL; arr = arr->next)
+ {
+ struct grub_diskfilter_pv *m;
+ for (m = arr->pvs; m; m = m->next)
+ if (m->disk && m->disk->id == disk->id
+ && m->disk->dev->id == disk->dev->id
+ && m->part_start == grub_partition_get_start (disk->partition)
+ && m->part_size == grub_disk_native_sectors (disk))
+ return 0;
+ }
+
+ for (diskfilter = grub_diskfilter_list; diskfilter; diskfilter = diskfilter->next)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("Scanning for %s devices on disk %s",
+ diskfilter->name, name);
+#endif
+ id.uuid = 0;
+ id.uuidlen = 0;
+ arr = diskfilter->detect (disk, &id, &start_sector);
+ if (arr &&
+ (! insert_array (disk, &id, arr, start_sector, diskfilter)))
+ {
+ if (id.uuidlen)
+ grub_free (id.uuid);
+ return 0;
+ }
+ if (arr && id.uuidlen)
+ grub_free (id.uuid);
+
+ /* This error usually means it's not diskfilter, no need to display
+ it. */
+ if (grub_errno != GRUB_ERR_OUT_OF_RANGE)
+ grub_print_error ();
+
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ return 0;
+}
+
+static int
+scan_disk (const char *name, int accept_diskfilter)
+{
+ grub_disk_t disk;
+ static int scan_depth = 0;
+
+ if (!accept_diskfilter && is_valid_diskfilter_name (name))
+ return 0;
+
+ if (scan_depth > 100)
+ return 0;
+
+ scan_depth++;
+ disk = grub_disk_open (name);
+ if (!disk)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ scan_depth--;
+ return 0;
+ }
+ scan_disk_partition_iter (disk, 0, (void *) name);
+ grub_partition_iterate (disk, scan_disk_partition_iter, (void *) name);
+ grub_disk_close (disk);
+ scan_depth--;
+ return 0;
+}
+
+static int
+scan_disk_hook (const char *name, void *data __attribute__ ((unused)))
+{
+ return scan_disk (name, 0);
+}
+
+static void
+scan_devices (const char *arname)
+{
+ grub_disk_dev_t p;
+ grub_disk_pull_t pull;
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_lv *lv = NULL;
+ int scan_depth;
+ int need_rescan;
+
+ for (pull = 0; pull < GRUB_DISK_PULL_MAX; pull++)
+ for (p = grub_disk_dev_list; p; p = p->next)
+ if (p->id != GRUB_DISK_DEVICE_DISKFILTER_ID
+ && p->disk_iterate)
+ {
+ if ((p->disk_iterate) (scan_disk_hook, NULL, pull))
+ return;
+ if (arname && is_lv_readable (find_lv (arname), 1))
+ return;
+ }
+
+ scan_depth = 0;
+ need_rescan = 1;
+ while (need_rescan && scan_depth++ < 100)
+ {
+ need_rescan = 0;
+ for (vg = array_list; vg; vg = vg->next)
+ {
+ if (vg->lvs)
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (!lv->scanned && lv->fullname && lv->became_readable_at)
+ {
+ scan_disk (lv->fullname, 1);
+ lv->scanned = 1;
+ need_rescan = 1;
+ }
+ }
+ }
+
+ if (need_rescan)
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "DISKFILTER scan depth exceeded");
+}
+
+static int
+grub_diskfilter_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_diskfilter_vg *array;
+ int islcnt = 0;
+
+ if (pull == GRUB_DISK_PULL_RESCAN)
+ {
+ islcnt = inscnt + 1;
+ scan_devices (NULL);
+ }
+
+ if (pull != GRUB_DISK_PULL_NONE && pull != GRUB_DISK_PULL_RESCAN)
+ return 0;
+
+ for (array = array_list; array; array = array->next)
+ {
+ struct grub_diskfilter_lv *lv;
+ if (array->lvs)
+ for (lv = array->lvs; lv; lv = lv->next)
+ if (lv->visible && lv->fullname && lv->became_readable_at >= islcnt)
+ {
+ if (hook (lv->fullname, hook_data))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef GRUB_UTIL
+static grub_disk_memberlist_t
+grub_diskfilter_memberlist (grub_disk_t disk)
+{
+ struct grub_diskfilter_lv *lv = disk->data;
+ grub_disk_memberlist_t list = NULL, tmp;
+ struct grub_diskfilter_pv *pv;
+ grub_disk_pull_t pull;
+ grub_disk_dev_t p;
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_lv *lv2 = NULL;
+
+ if (!lv->vg->pvs)
+ return NULL;
+
+ pv = lv->vg->pvs;
+ while (pv && pv->disk)
+ pv = pv->next;
+
+ for (pull = 0; pv && pull < GRUB_DISK_PULL_MAX; pull++)
+ for (p = grub_disk_dev_list; pv && p; p = p->next)
+ if (p->id != GRUB_DISK_DEVICE_DISKFILTER_ID
+ && p->disk_iterate)
+ {
+ (p->disk_iterate) (scan_disk_hook, NULL, pull);
+ while (pv && pv->disk)
+ pv = pv->next;
+ }
+
+ for (vg = array_list; pv && vg; vg = vg->next)
+ {
+ if (vg->lvs)
+ for (lv2 = vg->lvs; pv && lv2; lv2 = lv2->next)
+ if (!lv2->scanned && lv2->fullname && lv2->became_readable_at)
+ {
+ scan_disk (lv2->fullname, 1);
+ lv2->scanned = 1;
+ while (pv && pv->disk)
+ pv = pv->next;
+ }
+ }
+
+ for (pv = lv->vg->pvs; pv; pv = pv->next)
+ {
+ if (!pv->disk)
+ {
+ /* TRANSLATORS: This message kicks in during the detection of
+ which modules needs to be included in core image. This happens
+ in the case of degraded RAID and means that autodetection may
+ fail to include some of modules. It's an installation time
+ message, not runtime message. */
+ grub_util_warn (_("Couldn't find physical volume `%s'."
+ " Some modules may be missing from core image."),
+ pv->name);
+ continue;
+ }
+ tmp = grub_malloc (sizeof (*tmp));
+ tmp->disk = pv->disk;
+ tmp->next = list;
+ list = tmp;
+ }
+
+ return list;
+}
+
+void
+grub_diskfilter_get_partmap (grub_disk_t disk,
+ void (*cb) (const char *pm, void *data),
+ void *data)
+{
+ struct grub_diskfilter_lv *lv = disk->data;
+ struct grub_diskfilter_pv *pv;
+
+ if (lv->vg->pvs)
+ for (pv = lv->vg->pvs; pv; pv = pv->next)
+ {
+ grub_size_t s;
+ if (!pv->disk)
+ {
+ /* TRANSLATORS: This message kicks in during the detection of
+ which modules needs to be included in core image. This happens
+ in the case of degraded RAID and means that autodetection may
+ fail to include some of modules. It's an installation time
+ message, not runtime message. */
+ grub_util_warn (_("Couldn't find physical volume `%s'."
+ " Some modules may be missing from core image."),
+ pv->name);
+ continue;
+ }
+ for (s = 0; pv->partmaps[s]; s++)
+ cb (pv->partmaps[s], data);
+ }
+}
+
+static const char *
+grub_diskfilter_getname (struct grub_disk *disk)
+{
+ struct grub_diskfilter_lv *array = disk->data;
+
+ return array->vg->driver->name;
+}
+#endif
+
+static inline char
+hex2ascii (int c)
+{
+ if (c >= 10)
+ return 'a' + c - 10;
+ return c + '0';
+}
+
+static struct grub_diskfilter_lv *
+find_lv (const char *name)
+{
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_lv *lv = NULL;
+
+ for (vg = array_list; vg; vg = vg->next)
+ {
+ if (vg->lvs)
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (((lv->fullname && grub_strcmp (lv->fullname, name) == 0)
+ || (lv->idname && grub_strcmp (lv->idname, name) == 0))
+ && is_lv_readable (lv, 0))
+ return lv;
+ }
+ return NULL;
+}
+
+static grub_err_t
+grub_diskfilter_open (const char *name, grub_disk_t disk)
+{
+ struct grub_diskfilter_lv *lv;
+
+ if (!is_valid_diskfilter_name (name))
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown DISKFILTER device %s",
+ name);
+
+ lv = find_lv (name);
+
+ if (! lv)
+ {
+ scan_devices (name);
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+ lv = find_lv (name);
+ }
+
+ if (!lv)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown DISKFILTER device %s",
+ name);
+
+ disk->id = lv->number;
+ disk->data = lv;
+
+ disk->total_sectors = lv->size;
+ disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE;
+ return 0;
+}
+
+static void
+grub_diskfilter_close (grub_disk_t disk __attribute ((unused)))
+{
+ return;
+}
+
+static grub_err_t
+read_lv (struct grub_diskfilter_lv *lv, grub_disk_addr_t sector,
+ grub_size_t size, char *buf);
+
+grub_err_t
+grub_diskfilter_read_node (const struct grub_diskfilter_node *node,
+ grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ /* Check whether we actually know the physical volume we want to
+ read from. */
+ if (node->pv)
+ {
+ if (node->pv->disk)
+ return grub_disk_read (node->pv->disk, sector + node->start
+ + node->pv->start_sector,
+ 0, size << GRUB_DISK_SECTOR_BITS, buf);
+ else
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ N_("physical volume %s not found"), node->pv->name);
+
+ }
+ if (node->lv)
+ return read_lv (node->lv, sector + node->start, size, buf);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown node '%s'", node->name);
+}
+
+
+static grub_err_t
+validate_segment (struct grub_diskfilter_segment *seg);
+
+static grub_err_t
+validate_lv (struct grub_diskfilter_lv *lv)
+{
+ unsigned int i;
+ if (!lv)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown volume");
+
+ if (!lv->vg || lv->vg->extent_size == 0)
+ return grub_error (GRUB_ERR_READ_ERROR, "invalid volume");
+
+ for (i = 0; i < lv->segment_count; i++)
+ {
+ grub_err_t err;
+ err = validate_segment (&lv->segments[i]);
+ if (err)
+ return err;
+ }
+ return GRUB_ERR_NONE;
+}
+
+
+static grub_err_t
+validate_node (const struct grub_diskfilter_node *node)
+{
+ /* Check whether we actually know the physical volume we want to
+ read from. */
+ if (node->pv)
+ return GRUB_ERR_NONE;
+ if (node->lv)
+ return validate_lv (node->lv);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown node '%s'", node->name);
+}
+
+static grub_err_t
+validate_segment (struct grub_diskfilter_segment *seg)
+{
+ grub_err_t err;
+
+ if (seg->stripe_size == 0 || seg->node_count == 0)
+ return grub_error(GRUB_ERR_BAD_FS, "invalid segment");
+
+ switch (seg->type)
+ {
+ case GRUB_DISKFILTER_RAID10:
+ {
+ grub_uint8_t near, far;
+ near = seg->layout & 0xFF;
+ far = (seg->layout >> 8) & 0xFF;
+ if ((seg->layout >> 16) == 0 && far == 0)
+ return grub_error(GRUB_ERR_BAD_FS, "invalid segment");
+ if (near > seg->node_count)
+ return grub_error(GRUB_ERR_BAD_FS, "invalid segment");
+ break;
+ }
+
+ case GRUB_DISKFILTER_STRIPED:
+ case GRUB_DISKFILTER_MIRROR:
+ break;
+
+ case GRUB_DISKFILTER_RAID4:
+ case GRUB_DISKFILTER_RAID5:
+ if (seg->node_count <= 1)
+ return grub_error(GRUB_ERR_BAD_FS, "invalid segment");
+ break;
+
+ case GRUB_DISKFILTER_RAID6:
+ if (seg->node_count <= 2)
+ return grub_error(GRUB_ERR_BAD_FS, "invalid segment");
+ break;
+
+ default:
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level %d", seg->type);
+ }
+
+ unsigned i;
+ for (i = 0; i < seg->node_count; i++)
+ {
+ err = validate_node (&seg->nodes[i]);
+ if (err)
+ return err;
+ }
+ return GRUB_ERR_NONE;
+
+}
+
+static grub_err_t
+read_segment (struct grub_diskfilter_segment *seg, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_err_t err;
+ switch (seg->type)
+ {
+ case GRUB_DISKFILTER_STRIPED:
+ if (seg->node_count == 1)
+ return grub_diskfilter_read_node (&seg->nodes[0],
+ sector, size, buf);
+ /* Fallthrough. */
+ case GRUB_DISKFILTER_MIRROR:
+ case GRUB_DISKFILTER_RAID10:
+ {
+ grub_disk_addr_t read_sector, far_ofs;
+ grub_uint64_t disknr, b, near, far, ofs;
+ unsigned int i, j;
+
+ read_sector = grub_divmod64 (sector, seg->stripe_size, &b);
+ far = ofs = near = 1;
+ far_ofs = 0;
+
+ if (seg->type == 1)
+ near = seg->node_count;
+ else if (seg->type == 10)
+ {
+ near = seg->layout & 0xFF;
+ far = (seg->layout >> 8) & 0xFF;
+ if (seg->layout >> 16)
+ {
+ ofs = far;
+ far_ofs = 1;
+ }
+ else
+ far_ofs = grub_divmod64 (seg->raid_member_size,
+ far * seg->stripe_size, 0);
+
+ far_ofs *= seg->stripe_size;
+ }
+
+ read_sector = grub_divmod64 (read_sector * near,
+ seg->node_count,
+ &disknr);
+
+ ofs *= seg->stripe_size;
+ read_sector *= ofs;
+
+ while (1)
+ {
+ grub_size_t read_size;
+
+ read_size = seg->stripe_size - b;
+ if (read_size > size)
+ read_size = size;
+
+ err = 0;
+ for (i = 0; i < near; i++)
+ {
+ unsigned int k;
+
+ k = disknr;
+ err = 0;
+ for (j = 0; j < far; j++)
+ {
+ if (grub_errno == GRUB_ERR_READ_ERROR
+ || grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
+ grub_errno = GRUB_ERR_NONE;
+
+ err = grub_diskfilter_read_node (&seg->nodes[k],
+ read_sector
+ + j * far_ofs + b,
+ read_size,
+ buf);
+ if (! err)
+ break;
+ else if (err != GRUB_ERR_READ_ERROR
+ && err != GRUB_ERR_UNKNOWN_DEVICE)
+ return err;
+ k++;
+ if (k == seg->node_count)
+ k = 0;
+ }
+
+ if (! err)
+ break;
+
+ disknr++;
+ if (disknr == seg->node_count)
+ {
+ disknr = 0;
+ read_sector += ofs;
+ }
+ }
+
+ if (err)
+ return err;
+
+ buf += read_size << GRUB_DISK_SECTOR_BITS;
+ size -= read_size;
+ if (! size)
+ return GRUB_ERR_NONE;
+
+ b = 0;
+ disknr += (near - i);
+ while (disknr >= seg->node_count)
+ {
+ disknr -= seg->node_count;
+ read_sector += ofs;
+ }
+ }
+ }
+
+ case GRUB_DISKFILTER_RAID4:
+ case GRUB_DISKFILTER_RAID5:
+ case GRUB_DISKFILTER_RAID6:
+ {
+ grub_disk_addr_t read_sector;
+ grub_uint64_t b, p, n, disknr, e;
+
+ /* n = 1 for level 4 and 5, 2 for level 6. */
+ n = seg->type / 3;
+
+ /* Find the first sector to read. */
+ read_sector = grub_divmod64 (sector, seg->stripe_size, &b);
+ read_sector = grub_divmod64 (read_sector, seg->node_count - n,
+ &disknr);
+ if (seg->type >= 5)
+ {
+ grub_divmod64 (read_sector, seg->node_count, &p);
+
+ if (! (seg->layout & GRUB_RAID_LAYOUT_RIGHT_MASK))
+ p = seg->node_count - 1 - p;
+
+ if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ disknr += p + n;
+ }
+ else
+ {
+ grub_uint32_t q;
+
+ q = p + (n - 1);
+ if (q >= seg->node_count)
+ q -= seg->node_count;
+
+ if (disknr >= p)
+ disknr += n;
+ else if (disknr >= q)
+ disknr += q + 1;
+ }
+
+ if (disknr >= seg->node_count)
+ disknr -= seg->node_count;
+ }
+ else
+ p = seg->node_count - n;
+ read_sector *= seg->stripe_size;
+
+ while (1)
+ {
+ grub_size_t read_size;
+ int next_level;
+
+ read_size = seg->stripe_size - b;
+ if (read_size > size)
+ read_size = size;
+
+ e = 0;
+ /* Reset read error. */
+ if (grub_errno == GRUB_ERR_READ_ERROR
+ || grub_errno == GRUB_ERR_UNKNOWN_DEVICE)
+ grub_errno = GRUB_ERR_NONE;
+
+ err = grub_diskfilter_read_node (&seg->nodes[disknr],
+ read_sector + b,
+ read_size,
+ buf);
+
+ if ((err) && (err != GRUB_ERR_READ_ERROR
+ && err != GRUB_ERR_UNKNOWN_DEVICE))
+ return err;
+ e++;
+
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ if (seg->type == GRUB_DISKFILTER_RAID6)
+ {
+ err = ((grub_raid6_recover_func) ?
+ (*grub_raid6_recover_func) (seg, disknr, p,
+ buf, read_sector + b,
+ read_size) :
+ grub_error (GRUB_ERR_BAD_DEVICE,
+ N_("module `%s' isn't loaded"),
+ "raid6rec"));
+ }
+ else
+ {
+ err = ((grub_raid5_recover_func) ?
+ (*grub_raid5_recover_func) (seg, disknr,
+ buf, read_sector + b,
+ read_size) :
+ grub_error (GRUB_ERR_BAD_DEVICE,
+ N_("module `%s' isn't loaded"),
+ "raid5rec"));
+ }
+
+ if (err)
+ return err;
+ }
+
+ buf += read_size << GRUB_DISK_SECTOR_BITS;
+ size -= read_size;
+ sector += read_size;
+ if (! size)
+ break;
+
+ b = 0;
+ disknr++;
+
+ if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ if (disknr == seg->node_count)
+ disknr = 0;
+
+ next_level = (disknr == p);
+ }
+ else
+ {
+ if (disknr == p)
+ disknr += n;
+
+ next_level = (disknr >= seg->node_count);
+ }
+
+ if (next_level)
+ {
+ read_sector += seg->stripe_size;
+
+ if (seg->type >= 5)
+ {
+ if (seg->layout & GRUB_RAID_LAYOUT_RIGHT_MASK)
+ p = (p == seg->node_count - 1) ? 0 : p + 1;
+ else
+ p = (p == 0) ? seg->node_count - 1 : p - 1;
+
+ if (seg->layout & GRUB_RAID_LAYOUT_SYMMETRIC_MASK)
+ {
+ disknr = p + n;
+ if (disknr >= seg->node_count)
+ disknr -= seg->node_count;
+ }
+ else
+ {
+ disknr -= seg->node_count;
+ if ((disknr >= p && disknr < p + n)
+ || (disknr + seg->node_count >= p
+ && disknr + seg->node_count < p + n))
+ disknr = p + n;
+ if (disknr >= seg->node_count)
+ disknr -= seg->node_count;
+ }
+ }
+ else
+ disknr = 0;
+ }
+ }
+ }
+ return GRUB_ERR_NONE;
+ default:
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level %d", seg->type);
+ }
+}
+
+static grub_err_t
+read_lv (struct grub_diskfilter_lv *lv, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ if (!lv)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown volume");
+
+ while (size)
+ {
+ grub_err_t err = 0;
+ struct grub_diskfilter_vg *vg = lv->vg;
+ struct grub_diskfilter_segment *seg = lv->segments;
+ grub_uint64_t extent;
+ grub_uint64_t to_read;
+
+ extent = grub_divmod64 (sector, vg->extent_size, NULL);
+
+ /* Find the right segment. */
+ {
+ unsigned int i;
+ for (i = 0; i < lv->segment_count; i++)
+ {
+ if ((seg->start_extent <= extent)
+ && ((seg->start_extent + seg->extent_count) > extent))
+ break;
+ seg++;
+ }
+ if (i == lv->segment_count)
+ return grub_error (GRUB_ERR_READ_ERROR, "incorrect segment");
+ }
+ to_read = ((seg->start_extent + seg->extent_count)
+ * vg->extent_size) - sector;
+ if (to_read > size)
+ to_read = size;
+
+ err = read_segment (seg, sector - seg->start_extent * vg->extent_size,
+ to_read, buf);
+ if (err)
+ return err;
+
+ size -= to_read;
+ sector += to_read;
+ buf += to_read << GRUB_DISK_SECTOR_BITS;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_diskfilter_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ return read_lv (disk->data, sector, size, buf);
+}
+
+static grub_err_t
+grub_diskfilter_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_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "diskfilter writes are not supported");
+}
+
+struct grub_diskfilter_vg *
+grub_diskfilter_get_vg_by_uuid (grub_size_t uuidlen, char *uuid)
+{
+ struct grub_diskfilter_vg *p;
+
+ for (p = array_list; p != NULL; p = p->next)
+ if ((p->uuid_len == uuidlen) &&
+ (! grub_memcmp (p->uuid, uuid, p->uuid_len)))
+ return p;
+ return NULL;
+}
+
+grub_err_t
+grub_diskfilter_vg_register (struct grub_diskfilter_vg *vg)
+{
+ struct grub_diskfilter_lv *lv, *p;
+ struct grub_diskfilter_vg *vgp;
+ unsigned i;
+
+ grub_dprintf ("diskfilter", "Found array %s\n", vg->name);
+#ifdef GRUB_UTIL
+ grub_util_info ("Found array %s", vg->name);
+#endif
+
+ for (lv = vg->lvs; lv; lv = lv->next)
+ {
+ grub_err_t err;
+
+ /* RAID 1 and single-disk RAID 0 don't use a chunksize but code
+ assumes one so set one. */
+ for (i = 0; i < lv->segment_count; i++)
+ {
+ if (lv->segments[i].type == 1)
+ lv->segments[i].stripe_size = 64;
+ if (lv->segments[i].type == GRUB_DISKFILTER_STRIPED
+ && lv->segments[i].node_count == 1
+ && lv->segments[i].stripe_size == 0)
+ lv->segments[i].stripe_size = 64;
+ }
+
+ err = validate_lv(lv);
+ if (err)
+ return err;
+ lv->number = lv_num++;
+
+ if (lv->fullname)
+ {
+ grub_size_t len;
+ int max_used_number = 0, need_new_name = 0;
+ len = grub_strlen (lv->fullname);
+ for (vgp = array_list; vgp; vgp = vgp->next)
+ for (p = vgp->lvs; p; p = p->next)
+ {
+ int cur_num;
+ char *num;
+ const char *end;
+ if (!p->fullname)
+ continue;
+ if (grub_strncmp (p->fullname, lv->fullname, len) != 0)
+ continue;
+ if (p->fullname[len] == 0)
+ {
+ need_new_name = 1;
+ continue;
+ }
+ num = p->fullname + len + 1;
+ if (!grub_isdigit (num[0]))
+ continue;
+ cur_num = grub_strtoul (num, &end, 10);
+ if (end[0])
+ continue;
+ if (cur_num > max_used_number)
+ max_used_number = cur_num;
+ }
+ if (need_new_name)
+ {
+ char *tmp;
+ tmp = grub_xasprintf ("%s_%d", lv->fullname, max_used_number + 1);
+ if (!tmp)
+ return grub_errno;
+ grub_free (lv->fullname);
+ lv->fullname = tmp;
+ }
+ }
+ }
+ /* Add our new array to the list. */
+ vg->next = array_list;
+ array_list = vg;
+ return GRUB_ERR_NONE;
+}
+
+struct grub_diskfilter_vg *
+grub_diskfilter_make_raid (grub_size_t uuidlen, char *uuid, int nmemb,
+ const char *name, grub_uint64_t disk_size,
+ grub_uint64_t stripe_size,
+ int layout, int level)
+{
+ struct grub_diskfilter_vg *array;
+ int i;
+ grub_size_t j;
+ grub_uint64_t totsize;
+ struct grub_diskfilter_pv *pv;
+ grub_err_t err;
+
+ switch (level)
+ {
+ case 1:
+ totsize = disk_size;
+ break;
+
+ case 10:
+ {
+ int n;
+ n = layout & 0xFF;
+ if (n == 1)
+ n = (layout >> 8) & 0xFF;
+ if (n == 0)
+ {
+ grub_free (uuid);
+ return NULL;
+ }
+
+ totsize = grub_divmod64 (nmemb * disk_size, n, 0);
+ }
+ break;
+
+ case 0:
+ case 4:
+ case 5:
+ case 6:
+ totsize = (nmemb - ((unsigned) level / 3U)) * disk_size;
+ break;
+
+ default:
+ grub_free (uuid);
+ return NULL;
+ }
+
+ array = grub_diskfilter_get_vg_by_uuid (uuidlen, uuid);
+ if (array)
+ {
+ if (array->lvs && array->lvs->size < totsize)
+ {
+ array->lvs->size = totsize;
+ if (array->lvs->segments)
+ array->lvs->segments->extent_count = totsize;
+ }
+
+ if (array->lvs && array->lvs->segments
+ && array->lvs->segments->raid_member_size > disk_size)
+ array->lvs->segments->raid_member_size = disk_size;
+
+ grub_free (uuid);
+ return array;
+ }
+ array = grub_zalloc (sizeof (*array));
+ if (!array)
+ {
+ grub_free (uuid);
+ return NULL;
+ }
+ array->uuid = uuid;
+ array->uuid_len = uuidlen;
+ if (name)
+ {
+ /* Strip off the homehost if present. */
+ char *colon = grub_strchr (name, ':');
+ char *new_name = grub_xasprintf ("md/%s",
+ colon ? colon + 1 : name);
+
+ if (! new_name)
+ goto fail;
+
+ array->name = new_name;
+ }
+
+ array->extent_size = 1;
+ array->lvs = grub_zalloc (sizeof (*array->lvs));
+ if (!array->lvs)
+ goto fail;
+ array->lvs->segment_count = 1;
+ array->lvs->visible = 1;
+ if (array->name)
+ {
+ array->lvs->name = grub_strdup (array->name);
+ if (!array->lvs->name)
+ goto fail;
+ array->lvs->fullname = grub_strdup (array->name);
+ if (!array->lvs->fullname)
+ goto fail;
+ }
+ array->lvs->vg = array;
+
+ array->lvs->idname = grub_malloc (sizeof ("mduuid/") + 2 * uuidlen);
+ if (!array->lvs->idname)
+ goto fail;
+
+ grub_memcpy (array->lvs->idname, "mduuid/", sizeof ("mduuid/") - 1);
+ for (j = 0; j < uuidlen; j++)
+ {
+ array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * j]
+ = hex2ascii (((unsigned char) uuid[j] >> 4));
+ array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * j + 1]
+ = hex2ascii (((unsigned char) uuid[j] & 0xf));
+ }
+ array->lvs->idname[sizeof ("mduuid/") - 1 + 2 * uuidlen] = '\0';
+
+ array->lvs->size = totsize;
+
+ array->lvs->segments = grub_zalloc (sizeof (*array->lvs->segments));
+ if (!array->lvs->segments)
+ goto fail;
+ array->lvs->segments->stripe_size = stripe_size;
+ array->lvs->segments->layout = layout;
+ array->lvs->segments->start_extent = 0;
+ array->lvs->segments->extent_count = totsize;
+ array->lvs->segments->type = level;
+ array->lvs->segments->node_count = nmemb;
+ array->lvs->segments->raid_member_size = disk_size;
+ array->lvs->segments->nodes
+ = grub_calloc (nmemb, sizeof (array->lvs->segments->nodes[0]));
+ array->lvs->segments->stripe_size = stripe_size;
+ for (i = 0; i < nmemb; i++)
+ {
+ pv = grub_zalloc (sizeof (*pv));
+ if (!pv)
+ goto fail;
+ pv->id.uuidlen = 0;
+ pv->id.id = i;
+ pv->next = array->pvs;
+ array->pvs = pv;
+ array->lvs->segments->nodes[i].pv = pv;
+ }
+
+ err = grub_diskfilter_vg_register (array);
+ if (err)
+ goto fail;
+
+ return array;
+
+ fail:
+ if (array->lvs)
+ {
+ grub_free (array->lvs->name);
+ grub_free (array->lvs->fullname);
+ grub_free (array->lvs->idname);
+ if (array->lvs->segments)
+ {
+ grub_free (array->lvs->segments->nodes);
+ grub_free (array->lvs->segments);
+ }
+ grub_free (array->lvs);
+ }
+ while (array->pvs)
+ {
+ pv = array->pvs->next;
+ grub_free (array->pvs);
+ array->pvs = pv;
+ }
+ grub_free (array->name);
+ grub_free (array->uuid);
+ grub_free (array);
+ return NULL;
+}
+
+static grub_err_t
+insert_array (grub_disk_t disk, const struct grub_diskfilter_pv_id *id,
+ struct grub_diskfilter_vg *array,
+ grub_disk_addr_t start_sector,
+ grub_diskfilter_t diskfilter __attribute__ ((unused)))
+{
+ struct grub_diskfilter_pv *pv;
+
+ grub_dprintf ("diskfilter", "Inserting %s (+%lld,%lld) into %s (%s)\n", disk->name,
+ (unsigned long long) grub_partition_get_start (disk->partition),
+ (unsigned long long) grub_disk_native_sectors (disk),
+ array->name, diskfilter->name);
+#ifdef GRUB_UTIL
+ grub_util_info ("Inserting %s (+%" GRUB_HOST_PRIuLONG_LONG ",%"
+ GRUB_HOST_PRIuLONG_LONG ") into %s (%s)\n", disk->name,
+ (unsigned long long) grub_partition_get_start (disk->partition),
+ (unsigned long long) grub_disk_native_sectors (disk),
+ array->name, diskfilter->name);
+ array->driver = diskfilter;
+#endif
+
+ for (pv = array->pvs; pv; pv = pv->next)
+ if (id->uuidlen == pv->id.uuidlen
+ && id->uuidlen
+ ? (grub_memcmp (pv->id.uuid, id->uuid, id->uuidlen) == 0)
+ : (pv->id.id == id->id))
+ {
+ struct grub_diskfilter_lv *lv;
+ /* FIXME: Check whether the update time of the superblocks are
+ the same. */
+ if (pv->disk && grub_disk_native_sectors (disk) >= pv->part_size)
+ return GRUB_ERR_NONE;
+ pv->disk = grub_disk_open (disk->name);
+ if (!pv->disk)
+ return grub_errno;
+ /* This could happen to LVM on RAID, pv->disk points to the
+ raid device, we shouldn't change it. */
+ pv->start_sector -= pv->part_start;
+ pv->part_start = grub_partition_get_start (disk->partition);
+ pv->part_size = grub_disk_native_sectors (disk);
+
+#ifdef GRUB_UTIL
+ {
+ grub_size_t s = 1;
+ grub_partition_t p;
+ for (p = disk->partition; p; p = p->parent)
+ s++;
+ pv->partmaps = xcalloc (s, sizeof (pv->partmaps[0]));
+ s = 0;
+ for (p = disk->partition; p; p = p->parent)
+ pv->partmaps[s++] = xstrdup (p->partmap->name);
+ pv->partmaps[s++] = 0;
+ }
+#endif
+ if (start_sector != (grub_uint64_t)-1)
+ pv->start_sector = start_sector;
+ pv->start_sector += pv->part_start;
+ /* Add the device to the array. */
+ for (lv = array->lvs; lv; lv = lv->next)
+ if (!lv->became_readable_at && lv->fullname && is_lv_readable (lv, 0))
+ lv->became_readable_at = ++inscnt;
+ break;
+ }
+
+ return 0;
+}
+
+static void
+free_array (void)
+{
+ while (array_list)
+ {
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_pv *pv;
+ struct grub_diskfilter_lv *lv;
+
+ vg = array_list;
+ array_list = array_list->next;
+
+ while ((pv = vg->pvs))
+ {
+ vg->pvs = pv->next;
+ grub_free (pv->name);
+ if (pv->disk)
+ grub_disk_close (pv->disk);
+ if (pv->id.uuidlen)
+ grub_free (pv->id.uuid);
+#ifdef GRUB_UTIL
+ grub_free (pv->partmaps);
+#endif
+ grub_free (pv->internal_id);
+ grub_free (pv);
+ }
+
+ while ((lv = vg->lvs))
+ {
+ unsigned i;
+ vg->lvs = lv->next;
+ grub_free (lv->fullname);
+ grub_free (lv->name);
+ grub_free (lv->idname);
+ for (i = 0; i < lv->segment_count; i++)
+ grub_free (lv->segments[i].nodes);
+ grub_free (lv->segments);
+ grub_free (lv->internal_id);
+ grub_free (lv);
+ }
+
+ grub_free (vg->uuid);
+ grub_free (vg->name);
+ grub_free (vg);
+ }
+
+ array_list = 0;
+}
+
+#ifdef GRUB_UTIL
+struct grub_diskfilter_pv *
+grub_diskfilter_get_pv_from_disk (grub_disk_t disk,
+ struct grub_diskfilter_vg **vg_out)
+{
+ struct grub_diskfilter_pv *pv;
+ struct grub_diskfilter_vg *vg;
+
+ scan_disk (disk->name, 1);
+ for (vg = array_list; vg; vg = vg->next)
+ for (pv = vg->pvs; pv; pv = pv->next)
+ {
+ if (pv->disk && pv->disk->id == disk->id
+ && pv->disk->dev->id == disk->dev->id
+ && pv->part_start == grub_partition_get_start (disk->partition)
+ && pv->part_size == grub_disk_native_sectors (disk))
+ {
+ if (vg_out)
+ *vg_out = vg;
+ return pv;
+ }
+ }
+ return NULL;
+}
+#endif
+
+static struct grub_disk_dev grub_diskfilter_dev =
+ {
+ .name = "diskfilter",
+ .id = GRUB_DISK_DEVICE_DISKFILTER_ID,
+ .disk_iterate = grub_diskfilter_iterate,
+ .disk_open = grub_diskfilter_open,
+ .disk_close = grub_diskfilter_close,
+ .disk_read = grub_diskfilter_read,
+ .disk_write = grub_diskfilter_write,
+#ifdef GRUB_UTIL
+ .disk_memberlist = grub_diskfilter_memberlist,
+ .disk_raidname = grub_diskfilter_getname,
+#endif
+ .next = 0
+ };
+
+
+GRUB_MOD_INIT(diskfilter)
+{
+ grub_disk_dev_register (&grub_diskfilter_dev);
+}
+
+GRUB_MOD_FINI(diskfilter)
+{
+ grub_disk_dev_unregister (&grub_diskfilter_dev);
+ free_array ();
+}
diff --git a/grub-core/disk/dmraid_nvidia.c b/grub-core/disk/dmraid_nvidia.c
new file mode 100644
index 0000000..6372663
--- /dev/null
+++ b/grub-core/disk/dmraid_nvidia.c
@@ -0,0 +1,196 @@
+/* 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/diskfilter.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define NV_SIGNATURES 4
+
+#define NV_IDLE 0
+#define NV_SCDB_INIT_RAID 2
+#define NV_SCDB_REBUILD_RAID 3
+#define NV_SCDB_UPGRADE_RAID 4
+#define NV_SCDB_SYNC_RAID 5
+
+#define NV_LEVEL_UNKNOWN 0x00
+#define NV_LEVEL_JBOD 0xFF
+#define NV_LEVEL_0 0x80
+#define NV_LEVEL_1 0x81
+#define NV_LEVEL_3 0x83
+#define NV_LEVEL_5 0x85
+#define NV_LEVEL_10 0x8a
+#define NV_LEVEL_1_0 0x8180
+
+#define NV_ARRAY_FLAG_BOOT 1 /* BIOS use only. */
+#define NV_ARRAY_FLAG_ERROR 2 /* Degraded or offline. */
+#define NV_ARRAY_FLAG_PARITY_VALID 4 /* RAID-3/5 parity valid. */
+
+struct grub_nv_array
+{
+ grub_uint32_t version;
+ grub_uint32_t signature[NV_SIGNATURES];
+ grub_uint8_t raid_job_code;
+ grub_uint8_t stripe_width;
+ grub_uint8_t total_volumes;
+ grub_uint8_t original_width;
+ grub_uint32_t raid_level;
+ grub_uint32_t stripe_block_size;
+ grub_uint32_t stripe_block_size_bytes;
+ grub_uint32_t stripe_block_size_log2;
+ grub_uint32_t stripe_mask;
+ grub_uint32_t stripe_size;
+ grub_uint32_t stripe_size_bytes;
+ grub_uint32_t raid_job_mask;
+ grub_uint32_t original_capacity;
+ grub_uint32_t flags;
+};
+
+#define NV_ID_LEN 8
+#define NV_ID_STRING "NVIDIA"
+#define NV_VERSION 100
+
+#define NV_PRODID_LEN 16
+#define NV_PRODREV_LEN 4
+
+struct grub_nv_super
+{
+ char vendor[NV_ID_LEN]; /* 0x00 - 0x07 ID string. */
+ grub_uint32_t size; /* 0x08 - 0x0B Size of metadata in dwords. */
+ grub_uint32_t chksum; /* 0x0C - 0x0F Checksum of this struct. */
+ grub_uint16_t version; /* 0x10 - 0x11 NV version. */
+ grub_uint8_t unit_number; /* 0x12 Disk index in array. */
+ grub_uint8_t reserved; /* 0x13. */
+ grub_uint32_t capacity; /* 0x14 - 0x17 Array capacity in sectors. */
+ grub_uint32_t sector_size; /* 0x18 - 0x1B Sector size. */
+ char prodid[NV_PRODID_LEN]; /* 0x1C - 0x2B Array product ID. */
+ char prodrev[NV_PRODREV_LEN]; /* 0x2C - 0x2F Array product revision */
+ grub_uint32_t unit_flags; /* 0x30 - 0x33 Flags for this disk */
+ struct grub_nv_array array; /* Array information */
+} GRUB_PACKED;
+
+static struct grub_diskfilter_vg *
+grub_dmraid_nv_detect (grub_disk_t disk,
+ struct grub_diskfilter_pv_id *id,
+ grub_disk_addr_t *start_sector)
+{
+ grub_disk_addr_t sector;
+ struct grub_nv_super sb;
+ int level;
+ grub_uint64_t disk_size;
+ grub_uint32_t capacity;
+ grub_uint8_t total_volumes;
+ char *uuid;
+
+ if (disk->partition)
+ /* Skip partition. */
+ return NULL;
+
+ sector = grub_disk_native_sectors (disk);
+ if (sector == GRUB_DISK_SIZE_UNKNOWN)
+ /* Not raid. */
+ return NULL;
+ sector -= 2;
+ if (grub_disk_read (disk, sector, 0, sizeof (sb), &sb))
+ return NULL;
+
+ if (grub_memcmp (sb.vendor, NV_ID_STRING, 6))
+ /* Not raid. */
+ return NULL;
+
+ if (sb.version != NV_VERSION)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unknown version: %d.%d", sb.version >> 8, sb.version & 0xFF);
+ return NULL;
+ }
+
+ capacity = grub_le_to_cpu32 (sb.capacity);
+ total_volumes = sb.array.total_volumes;
+
+ switch (sb.array.raid_level)
+ {
+ case NV_LEVEL_0:
+ level = 0;
+ if (total_volumes == 0)
+ /* Not RAID. */
+ return NULL;
+ disk_size = capacity / total_volumes;
+ break;
+
+ case NV_LEVEL_1:
+ level = 1;
+ disk_size = sb.capacity;
+ break;
+
+ case NV_LEVEL_5:
+ level = 5;
+ if (total_volumes == 0 || total_volumes == 1)
+ /* Not RAID. */
+ return NULL;
+ disk_size = capacity / (total_volumes - 1);
+ break;
+
+ default:
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level: %d", sb.array.raid_level);
+ return NULL;
+ }
+
+ uuid = grub_malloc (sizeof (sb.array.signature));
+ if (! uuid)
+ return NULL;
+
+ grub_memcpy (uuid, (char *) &sb.array.signature,
+ sizeof (sb.array.signature));
+
+ id->uuidlen = 0;
+ id->id = sb.unit_number;
+
+ *start_sector = 0;
+
+ return grub_diskfilter_make_raid (sizeof (sb.array.signature),
+ uuid, sb.array.total_volumes,
+ "nv", disk_size,
+ sb.array.stripe_block_size,
+ GRUB_RAID_LAYOUT_LEFT_ASYMMETRIC,
+ level);
+}
+
+static struct grub_diskfilter grub_dmraid_nv_dev =
+{
+ .name = "dmraid_nv",
+ .detect = grub_dmraid_nv_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT(dm_nv)
+{
+ grub_diskfilter_register_front (&grub_dmraid_nv_dev);
+}
+
+GRUB_MOD_FINI(dm_nv)
+{
+ grub_diskfilter_unregister (&grub_dmraid_nv_dev);
+}
diff --git a/grub-core/disk/efi/efidisk.c b/grub-core/disk/efi/efidisk.c
new file mode 100644
index 0000000..f077b5f
--- /dev/null
+++ b/grub-core/disk/efi/efidisk.c
@@ -0,0 +1,902 @@
+/*
+ * 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;
+ struct grub_efidisk_data *next;
+};
+
+/* 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;
+
+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, &block_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;
+
+ dp = grub_efi_get_device_path (*handle);
+ if (! dp)
+ continue;
+
+ ldp = grub_efi_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);
+ if (! bio)
+ /* This should not happen... Why? */
+ continue;
+
+ /* iPXE adds stub Block IO protocol to loaded image device handle. It is
+ completely non-functional and simply returns an error for every method.
+ So attempt to detect and skip it. Magic number is literal "iPXE" and
+ check block size as well */
+ /* FIXME: shoud we close it? We do not do it elsewhere */
+ if (bio->media && bio->media->media_id == 0x69505845U &&
+ bio->media->block_size == 1)
+ continue;
+
+ d = grub_malloc (sizeof (*d));
+ if (! d)
+ {
+ /* Uggh. */
+ grub_free (handles);
+ while (devices)
+ {
+ d = devices->next;
+ grub_free (devices);
+ devices = d;
+ }
+ return 0;
+ }
+
+ d->handle = *handle;
+ d->device_path = dp;
+ d->last_device_path = ldp;
+ d->block_io = bio;
+ 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 = grub_efi_duplicate_device_path (d->device_path);
+ if (! dp)
+ return 0;
+
+ ldp = grub_efi_find_last_device_path (dp);
+ if (! ldp)
+ return 0;
+
+ ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ ldp->length = sizeof (*ldp);
+
+ for (parent = devices; parent; parent = parent->next)
+ {
+ /* Ignore itself. */
+ if (parent == d)
+ continue;
+
+ if (grub_efi_compare_device_paths (parent->device_path, dp) == 0)
+ break;
+ }
+
+ grub_free (dp);
+ return parent;
+}
+
+static int
+is_child (struct grub_efidisk_data *child,
+ struct grub_efidisk_data *parent)
+{
+ grub_efi_device_path_t *dp, *ldp;
+ int ret;
+
+ dp = grub_efi_duplicate_device_path (child->device_path);
+ if (! dp)
+ return 0;
+
+ ldp = grub_efi_find_last_device_path (dp);
+ if (! ldp)
+ return 0;
+
+ ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ ldp->length = sizeof (*ldp);
+
+ ret = (grub_efi_compare_device_paths (dp, parent->device_path) == 0);
+ grub_free (dp);
+ return ret;
+}
+
+#define FOR_CHILDREN(p, dev) for (p = dev; p; p = p->next) if (is_child (p, d))
+
+/* 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 = grub_efi_compare_device_paths (grub_efi_find_last_device_path ((*p)->device_path),
+ grub_efi_find_last_device_path (d->device_path));
+ if (ret == 0)
+ ret = grub_efi_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;
+ /* Intentionally fall through. */
+ case GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE:
+ {
+ struct grub_efidisk_data *parent, *parent2;
+
+ parent = find_parent_device (devices, d);
+ if (!parent)
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("skipping orphaned partition: ");
+ grub_efi_print_device_path (d->device_path);
+#endif
+ break;
+ }
+ parent2 = find_parent_device (devices, parent);
+ if (parent2)
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("skipping subpartition: ");
+ grub_efi_print_device_path (d->device_path);
+#endif
+ /* Mark itself as used. */
+ d->last_device_path = 0;
+ break;
+ }
+ if (!parent->last_device_path)
+ {
+ d->last_device_path = 0;
+ break;
+ }
+ if (is_hard_drive)
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("adding a hard drive by a partition: ");
+ grub_efi_print_device_path (parent->device_path);
+#endif
+ add_device (&hd_devices, parent);
+ }
+ else
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("adding a cdrom by a partition: ");
+ grub_efi_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:
+#ifdef DEBUG_NAMES
+ grub_printf ("skipping other type: ");
+ grub_efi_print_device_path (d->device_path);
+#endif
+ /* For now, ignore the others. */
+ break;
+ }
+ }
+ else
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("skipping non-media: ");
+ grub_efi_print_device_path (d->device_path);
+#endif
+ }
+ }
+
+ /* 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;
+ int is_floppy = 0;
+
+ dp = d->last_device_path;
+ if (! dp)
+ continue;
+
+ /* Ghosts proudly presented by Apple. */
+ if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)
+ == GRUB_EFI_VENDOR_MEDIA_DEVICE_PATH_SUBTYPE)
+ {
+ grub_efi_vendor_device_path_t *vendor = (grub_efi_vendor_device_path_t *) dp;
+ const struct grub_efi_guid apple = GRUB_EFI_VENDOR_APPLE_GUID;
+
+ if (vendor->header.length == sizeof (*vendor)
+ && grub_memcmp (&vendor->vendor_guid, &apple,
+ sizeof (vendor->vendor_guid)) == 0
+ && find_parent_device (devices, d))
+ continue;
+ }
+
+ m = d->block_io->media;
+ if (GRUB_EFI_DEVICE_PATH_TYPE (dp) == GRUB_EFI_ACPI_DEVICE_PATH_TYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (dp)
+ == GRUB_EFI_ACPI_DEVICE_PATH_SUBTYPE)
+ {
+ grub_efi_acpi_device_path_t *acpi
+ = (grub_efi_acpi_device_path_t *) dp;
+ /* Floppy EISA ID. */
+ if (acpi->hid == 0x60441d0 || acpi->hid == 0x70041d0
+ || acpi->hid == 0x70141d1)
+ is_floppy = 1;
+ }
+ if (is_floppy)
+ {
+#ifdef DEBUG_NAMES
+ grub_printf ("adding a floppy: ");
+ grub_efi_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. */
+#ifdef DEBUG_NAMES
+ grub_printf ("adding a cdrom by guessing: ");
+ grub_efi_print_device_path (d->device_path);
+#endif
+ add_device (&cd_devices, d);
+ }
+ else
+ {
+ /* The default is a hard drive. */
+#ifdef DEBUG_NAMES
+ grub_printf ("adding a hard drive by guessing: ");
+ grub_efi_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 (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_efidisk_data *d;
+ char buf[16];
+ int count;
+
+ switch (pull)
+ {
+ case GRUB_DISK_PULL_NONE:
+ 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, hook_data))
+ return 1;
+ }
+ break;
+ case GRUB_DISK_PULL_REMOVABLE:
+ 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, hook_data))
+ 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, hook_data))
+ return 1;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int
+get_drive_number (const char *name)
+{
+ unsigned long drive;
+
+ if ((name[0] != 'f' && name[0] != 'h' && name[0] != 'c') || name[1] != 'd')
+ goto fail;
+
+ drive = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+
+ return (int) drive ;
+
+ fail:
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a efidisk");
+ return -1;
+}
+
+static struct grub_efidisk_data *
+get_device (struct grub_efidisk_data *devices, int num)
+{
+ struct grub_efidisk_data *d;
+
+ for (d = devices; d && num; d = d->next, num--)
+ ;
+
+ if (num == 0)
+ return d;
+
+ return 0;
+}
+
+static grub_err_t
+grub_efidisk_open (const char *name, struct grub_disk *disk)
+{
+ int num;
+ struct grub_efidisk_data *d = 0;
+ grub_efi_block_io_media_t *m;
+
+ grub_dprintf ("efidisk", "opening %s\n", name);
+
+ num = get_drive_number (name);
+ if (num < 0)
+ return grub_errno;
+
+ switch (name[0])
+ {
+ case 'f':
+ d = get_device (fd_devices, num);
+ break;
+ case 'c':
+ d = get_device (cd_devices, num);
+ break;
+ case 'h':
+ d = get_device (hd_devices, num);
+ break;
+ default:
+ /* Never reach here. */
+ break;
+ }
+
+ if (! d)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device");
+
+ disk->id = ((num << GRUB_CHAR_BIT) | 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, io align = %x\n",
+ m, (unsigned long long) m->last_block, m->block_size,
+ m->io_align);
+
+ /* Ensure required buffer alignment is a power of two (or is zero). */
+ if (m->io_align & (m->io_align - 1))
+ return grub_error (GRUB_ERR_IO, "invalid buffer alignment %d", m->io_align);
+
+ disk->total_sectors = m->last_block + 1;
+ /* Don't increase this value due to bug in some EFI. */
+ disk->max_agglomerate = 0xa0000 >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS);
+ if (m->block_size & (m->block_size - 1) || !m->block_size)
+ return grub_error (GRUB_ERR_IO, "invalid sector size %d",
+ m->block_size);
+ for (disk->log_sector_size = 0;
+ (1U << disk->log_sector_size) < m->block_size;
+ disk->log_sector_size++);
+ 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_efi_status_t
+grub_efidisk_readwrite (struct grub_disk *disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf, int wr)
+{
+ struct grub_efidisk_data *d;
+ grub_efi_block_io_t *bio;
+ grub_efi_status_t status;
+ grub_size_t io_align, num_bytes;
+ char *aligned_buf;
+
+ d = disk->data;
+ bio = d->block_io;
+
+ /* Set alignment to 1 if 0 specified */
+ io_align = bio->media->io_align ? bio->media->io_align : 1;
+ num_bytes = size << disk->log_sector_size;
+
+ if ((grub_addr_t) buf & (io_align - 1))
+ {
+ aligned_buf = grub_memalign (io_align, num_bytes);
+ if (! aligned_buf)
+ return GRUB_EFI_OUT_OF_RESOURCES;
+ if (wr)
+ grub_memcpy (aligned_buf, buf, num_bytes);
+ }
+ else
+ {
+ aligned_buf = buf;
+ }
+
+ status = efi_call_5 ((wr ? bio->write_blocks : bio->read_blocks), bio,
+ bio->media->media_id, (grub_efi_uint64_t) sector,
+ (grub_efi_uintn_t) num_bytes, aligned_buf);
+
+ if ((grub_addr_t) buf & (io_align - 1))
+ {
+ if (!wr)
+ grub_memcpy (buf, aligned_buf, num_bytes);
+ grub_free (aligned_buf);
+ }
+
+ return status;
+}
+
+static grub_err_t
+grub_efidisk_read (struct grub_disk *disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_efi_status_t status;
+
+ 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 = grub_efidisk_readwrite (disk, sector, size, buf, 0);
+
+ if (status == GRUB_EFI_NO_MEDIA)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name);
+ else if (status != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_READ_ERROR,
+ N_("failure reading sector 0x%llx from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+
+ 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)
+{
+ grub_efi_status_t status;
+
+ 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 = grub_efidisk_readwrite (disk, sector, size, (char *) buf, 1);
+
+ if (status == GRUB_EFI_NO_MEDIA)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE, N_("no media in `%s'"), disk->name);
+ else if (status != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_WRITE_ERROR,
+ N_("failure writing sector 0x%llx to `%s'"),
+ (unsigned long long) sector, disk->name);
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_disk_dev grub_efidisk_dev =
+ {
+ .name = "efidisk",
+ .id = GRUB_DISK_DEVICE_EFIDISK_ID,
+ .disk_iterate = grub_efidisk_iterate,
+ .disk_open = grub_efidisk_open,
+ .disk_close = grub_efidisk_close,
+ .disk_read = grub_efidisk_read,
+ .disk_write = grub_efidisk_write,
+ .next = 0
+ };
+
+void
+grub_efidisk_fini (void)
+{
+ free_devices (fd_devices);
+ free_devices (hd_devices);
+ free_devices (cd_devices);
+ fd_devices = 0;
+ hd_devices = 0;
+ cd_devices = 0;
+ grub_disk_dev_unregister (&grub_efidisk_dev);
+}
+
+void
+grub_efidisk_init (void)
+{
+ grub_disk_firmware_fini = grub_efidisk_fini;
+
+ enumerate_disks ();
+ grub_disk_dev_register (&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;
+ struct grub_efidisk_data *c;
+
+ devices = make_devices ();
+ FOR_CHILDREN (c, devices)
+ {
+ grub_efi_hard_drive_device_path_t *hd;
+
+ hd = (grub_efi_hard_drive_device_path_t *) c->last_device_path;
+
+ 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 << (disk->log_sector_size
+ - GRUB_DISK_SECTOR_BITS)))
+ && (grub_partition_get_len (disk->partition)
+ == (hd->partition_size << (disk->log_sector_size
+ - GRUB_DISK_SECTOR_BITS))))
+ {
+ handle = c->handle;
+ break;
+ }
+ }
+
+ free_devices (devices);
+
+ if (handle != 0)
+ return handle;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+#define NEEDED_BUFLEN sizeof ("XdXXXXXXXXXX")
+static inline int
+get_diskname_from_path_real (const grub_efi_device_path_t *path,
+ struct grub_efidisk_data *head,
+ char *buf)
+{
+ int count = 0;
+ struct grub_efidisk_data *d;
+ for (d = head, count = 0; d; d = d->next, count++)
+ if (grub_efi_compare_device_paths (d->device_path, path) == 0)
+ {
+ grub_snprintf (buf, NEEDED_BUFLEN - 1, "d%d", count);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int
+get_diskname_from_path (const grub_efi_device_path_t *path,
+ char *buf)
+{
+ if (get_diskname_from_path_real (path, hd_devices, buf + 1))
+ {
+ buf[0] = 'h';
+ return 1;
+ }
+
+ if (get_diskname_from_path_real (path, fd_devices, buf + 1))
+ {
+ buf[0] = 'f';
+ return 1;
+ }
+
+ if (get_diskname_from_path_real (path, cd_devices, buf + 1))
+ {
+ buf[0] = 'c';
+ return 1;
+ }
+ return 0;
+}
+
+/* Context for grub_efidisk_get_device_name. */
+struct grub_efidisk_get_device_name_ctx
+{
+ char *partition_name;
+ grub_efi_hard_drive_device_path_t *hd;
+};
+
+/* Helper for grub_efidisk_get_device_name.
+ Find the identical partition. */
+static int
+grub_efidisk_get_device_name_iter (grub_disk_t disk,
+ const grub_partition_t part, void *data)
+{
+ struct grub_efidisk_get_device_name_ctx *ctx = data;
+
+ if (grub_partition_get_start (part)
+ == (ctx->hd->partition_start << (disk->log_sector_size
+ - GRUB_DISK_SECTOR_BITS))
+ && grub_partition_get_len (part)
+ == (ctx->hd->partition_size << (disk->log_sector_size
+ - GRUB_DISK_SECTOR_BITS)))
+ {
+ ctx->partition_name = grub_partition_get_name (part);
+ return 1;
+ }
+
+ return 0;
+}
+
+char *
+grub_efidisk_get_device_name (grub_efi_handle_t *handle)
+{
+ grub_efi_device_path_t *dp, *ldp;
+ char device_name[NEEDED_BUFLEN];
+
+ dp = grub_efi_get_device_path (handle);
+ if (! dp)
+ return 0;
+
+ ldp = grub_efi_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_CDROM_DEVICE_PATH_SUBTYPE
+ || GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE))
+ {
+ struct grub_efidisk_get_device_name_ctx ctx;
+ char *dev_name;
+ grub_efi_device_path_t *dup_dp;
+ grub_disk_t parent = 0;
+
+ /* It is necessary to duplicate the device path so that GRUB
+ can overwrite it. */
+ dup_dp = grub_efi_duplicate_device_path (dp);
+ if (! dup_dp)
+ return 0;
+
+ while (1)
+ {
+ grub_efi_device_path_t *dup_ldp;
+ dup_ldp = grub_efi_find_last_device_path (dup_dp);
+ if (! dup_ldp)
+ break;
+
+ if (!(GRUB_EFI_DEVICE_PATH_TYPE (dup_ldp) == GRUB_EFI_MEDIA_DEVICE_PATH_TYPE
+ && (GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_CDROM_DEVICE_PATH_SUBTYPE
+ || GRUB_EFI_DEVICE_PATH_SUBTYPE (dup_ldp) == GRUB_EFI_HARD_DRIVE_DEVICE_PATH_SUBTYPE)))
+ break;
+
+ dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ dup_ldp->length = sizeof (*dup_ldp);
+ }
+
+ if (!get_diskname_from_path (dup_dp, device_name))
+ {
+ grub_free (dup_dp);
+ return 0;
+ }
+
+ parent = grub_disk_open (device_name);
+ grub_free (dup_dp);
+
+ if (! parent)
+ return 0;
+
+ /* Find a partition which matches the hard drive device path. */
+ ctx.partition_name = NULL;
+ ctx.hd = (grub_efi_hard_drive_device_path_t *) ldp;
+ if (ctx.hd->partition_start == 0
+ && (ctx.hd->partition_size << (parent->log_sector_size
+ - GRUB_DISK_SECTOR_BITS))
+ == grub_disk_native_sectors (parent))
+ {
+ dev_name = grub_strdup (parent->name);
+ }
+ else
+ {
+ grub_partition_iterate (parent, grub_efidisk_get_device_name_iter,
+ &ctx);
+
+ if (! ctx.partition_name)
+ {
+ /* No partition found. In most cases partition is embed in
+ the root path anyway, so this is not critical.
+ This happens only if partition is on partmap that GRUB
+ doesn't need to access root.
+ */
+ grub_disk_close (parent);
+ return grub_strdup (device_name);
+ }
+
+ dev_name = grub_xasprintf ("%s,%s", parent->name,
+ ctx.partition_name);
+ grub_free (ctx.partition_name);
+ }
+ grub_disk_close (parent);
+
+ return dev_name;
+ }
+ /* This may be guessed device - floppy, cdrom or entire disk. */
+ if (!get_diskname_from_path (dp, device_name))
+ return 0;
+ return grub_strdup (device_name);
+}
diff --git a/grub-core/disk/geli.c b/grub-core/disk/geli.c
new file mode 100644
index 0000000..2f34a35
--- /dev/null
+++ b/grub-core/disk/geli.c
@@ -0,0 +1,595 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2003,2007,2010,2011 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/>.
+ */
+
+/* This file is loosely based on FreeBSD geli implementation
+ (but no code was directly copied). FreeBSD geli is distributed under
+ following terms: */
+/*-
+ * Copyright (c) 2005-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <grub/cryptodisk.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/err.h>
+#include <grub/disk.h>
+#include <grub/crypto.h>
+#include <grub/partition.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Dirty trick to solve circular dependency. */
+#ifdef GRUB_UTIL
+
+#include <grub/util/misc.h>
+
+#undef GRUB_MD_SHA256
+#undef GRUB_MD_SHA512
+
+static const gcry_md_spec_t *
+grub_md_sha256_real (void)
+{
+ const gcry_md_spec_t *ret;
+ ret = grub_crypto_lookup_md_by_name ("sha256");
+ if (!ret)
+ grub_util_error ("%s", _("Couldn't load sha256"));
+ return ret;
+}
+
+static const gcry_md_spec_t *
+grub_md_sha512_real (void)
+{
+ const gcry_md_spec_t *ret;
+ ret = grub_crypto_lookup_md_by_name ("sha512");
+ if (!ret)
+ grub_util_error ("%s", _("Couldn't load sha512"));
+ return ret;
+}
+
+#define GRUB_MD_SHA256 grub_md_sha256_real()
+#define GRUB_MD_SHA512 grub_md_sha512_real()
+#endif
+
+struct grub_geli_key
+{
+ grub_uint8_t iv_key[64];
+ grub_uint8_t cipher_key[64];
+ grub_uint8_t hmac[64];
+} GRUB_PACKED;
+
+struct grub_geli_phdr
+{
+ grub_uint8_t magic[16];
+#define GELI_MAGIC "GEOM::ELI"
+ grub_uint32_t version;
+ grub_uint32_t flags;
+ grub_uint16_t alg;
+ grub_uint16_t keylen;
+ grub_uint16_t unused3[5];
+ grub_uint32_t sector_size;
+ grub_uint8_t keys_used;
+ grub_uint32_t niter;
+ grub_uint8_t salt[64];
+ struct grub_geli_key keys[2];
+} GRUB_PACKED;
+
+enum
+ {
+ GRUB_GELI_FLAGS_ONETIME = 1,
+ GRUB_GELI_FLAGS_BOOT = 2,
+ };
+
+/* FIXME: support version 0. */
+/* FIXME: support big-endian pre-version-4 volumes. */
+/* FIXME: support for keyfiles. */
+/* FIXME: support for HMAC. */
+const char *algorithms[] = {
+ [0x01] = "des",
+ [0x02] = "3des",
+ [0x03] = "blowfish",
+ [0x04] = "cast5",
+ /* FIXME: 0x05 is skipjack, but we don't have it. */
+ [0x0b] = "aes",
+ /* FIXME: 0x10 is null. */
+ [0x15] = "camellia128",
+ [0x16] = "aes"
+};
+
+#define MAX_PASSPHRASE 256
+
+static gcry_err_code_t
+geli_rekey (struct grub_cryptodisk *dev, grub_uint64_t zoneno)
+{
+ gcry_err_code_t gcry_err;
+ const struct {
+ char magic[4];
+ grub_uint64_t zone;
+ } GRUB_PACKED tohash
+ = { {'e', 'k', 'e', 'y'}, grub_cpu_to_le64 (zoneno) };
+ GRUB_PROPERLY_ALIGNED_ARRAY (key, GRUB_CRYPTO_MAX_MDLEN);
+
+ if (dev->hash->mdlen > GRUB_CRYPTO_MAX_MDLEN)
+ return GPG_ERR_INV_ARG;
+
+ grub_dprintf ("geli", "rekeying %" PRIuGRUB_UINT64_T " keysize=%d\n",
+ zoneno, dev->rekey_derived_size);
+ gcry_err = grub_crypto_hmac_buffer (dev->hash, dev->rekey_key, 64,
+ &tohash, sizeof (tohash), key);
+ if (gcry_err)
+ return gcry_err;
+
+ return grub_cryptodisk_setkey (dev, (grub_uint8_t *) key,
+ dev->rekey_derived_size);
+}
+
+static inline gcry_err_code_t
+make_uuid (const struct grub_geli_phdr *header,
+ char *uuid)
+{
+ grub_uint8_t uuidbin[GRUB_CRYPTODISK_MAX_UUID_LENGTH];
+ gcry_err_code_t err;
+ grub_uint8_t *iptr;
+ char *optr;
+
+ if (2 * GRUB_MD_SHA256->mdlen + 1 > GRUB_CRYPTODISK_MAX_UUID_LENGTH)
+ return GPG_ERR_TOO_LARGE;
+ err = grub_crypto_hmac_buffer (GRUB_MD_SHA256,
+ header->salt, sizeof (header->salt),
+ "uuid", sizeof ("uuid") - 1, uuidbin);
+ if (err)
+ return err;
+
+ optr = uuid;
+ for (iptr = uuidbin; iptr < &uuidbin[GRUB_MD_SHA256->mdlen]; iptr++)
+ {
+ grub_snprintf (optr, 3, "%02x", *iptr);
+ optr += 2;
+ }
+ *optr = 0;
+ return GPG_ERR_NO_ERROR;
+}
+
+#ifdef GRUB_UTIL
+
+#include <grub/emu/hostdisk.h>
+#include <grub/emu/misc.h>
+
+char *
+grub_util_get_geli_uuid (const char *dev)
+{
+ grub_util_fd_t fd;
+ grub_uint64_t s;
+ unsigned log_secsize;
+ grub_uint8_t hdr[512];
+ struct grub_geli_phdr *header;
+ char *uuid;
+ gcry_err_code_t err;
+
+ fd = grub_util_fd_open (dev, GRUB_UTIL_FD_O_RDONLY);
+
+ if (!GRUB_UTIL_FD_IS_VALID (fd))
+ return NULL;
+
+ s = grub_util_get_fd_size (fd, dev, &log_secsize);
+ s >>= log_secsize;
+ if (grub_util_fd_seek (fd, (s << log_secsize) - 512) < 0)
+ grub_util_error ("%s", _("couldn't read ELI metadata"));
+
+ uuid = xmalloc (GRUB_MD_SHA256->mdlen * 2 + 1);
+ if (grub_util_fd_read (fd, (void *) &hdr, 512) < 0)
+ grub_util_error ("%s", _("couldn't read ELI metadata"));
+
+ grub_util_fd_close (fd);
+
+ COMPILE_TIME_ASSERT (sizeof (header) <= 512);
+ header = (void *) &hdr;
+
+ /* Look for GELI magic sequence. */
+ if (grub_memcmp (header->magic, GELI_MAGIC, sizeof (GELI_MAGIC))
+ || grub_le_to_cpu32 (header->version) > 7
+ || grub_le_to_cpu32 (header->version) < 1)
+ grub_util_error ("%s", _("wrong ELI magic or version"));
+
+ err = make_uuid ((void *) &hdr, uuid);
+ if (err)
+ {
+ grub_free (uuid);
+ return NULL;
+ }
+
+ return uuid;
+}
+#endif
+
+static grub_cryptodisk_t
+configure_ciphers (grub_disk_t disk, const char *check_uuid,
+ int boot_only)
+{
+ grub_cryptodisk_t newdev;
+ struct grub_geli_phdr header;
+ grub_crypto_cipher_handle_t cipher = NULL, secondary_cipher = NULL;
+ const struct gcry_cipher_spec *ciph;
+ const char *ciphername = NULL;
+ gcry_err_code_t gcry_err;
+ char uuid[GRUB_CRYPTODISK_MAX_UUID_LENGTH];
+ grub_disk_addr_t sector;
+ grub_err_t err;
+
+ if (2 * GRUB_MD_SHA256->mdlen + 1 > GRUB_CRYPTODISK_MAX_UUID_LENGTH)
+ return NULL;
+
+ sector = grub_disk_native_sectors (disk);
+ if (sector == GRUB_DISK_SIZE_UNKNOWN || sector == 0)
+ return NULL;
+
+ /* Read the GELI header. */
+ err = grub_disk_read (disk, sector - 1, 0, sizeof (header), &header);
+ if (err)
+ return NULL;
+
+ /* Look for GELI magic sequence. */
+ if (grub_memcmp (header.magic, GELI_MAGIC, sizeof (GELI_MAGIC))
+ || grub_le_to_cpu32 (header.version) > 7
+ || grub_le_to_cpu32 (header.version) < 1)
+ {
+ grub_dprintf ("geli", "wrong magic %02x\n", header.magic[0]);
+ return NULL;
+ }
+
+ if ((grub_le_to_cpu32 (header.sector_size)
+ & (grub_le_to_cpu32 (header.sector_size) - 1))
+ || grub_le_to_cpu32 (header.sector_size) == 0)
+ {
+ grub_dprintf ("geli", "incorrect sector size %d\n",
+ grub_le_to_cpu32 (header.sector_size));
+ return NULL;
+ }
+
+ if (grub_le_to_cpu32 (header.flags) & GRUB_GELI_FLAGS_ONETIME)
+ {
+ grub_dprintf ("geli", "skipping one-time volume\n");
+ return NULL;
+ }
+
+ if (boot_only && !(grub_le_to_cpu32 (header.flags) & GRUB_GELI_FLAGS_BOOT))
+ {
+ grub_dprintf ("geli", "not a boot volume\n");
+ return NULL;
+ }
+
+ gcry_err = make_uuid (&header, uuid);
+ if (gcry_err)
+ {
+ grub_crypto_gcry_error (gcry_err);
+ return NULL;
+ }
+
+ if (check_uuid && grub_strcasecmp (check_uuid, uuid) != 0)
+ {
+ grub_dprintf ("geli", "%s != %s\n", uuid, check_uuid);
+ return NULL;
+ }
+
+ if (grub_le_to_cpu16 (header.alg) >= ARRAY_SIZE (algorithms)
+ || algorithms[grub_le_to_cpu16 (header.alg)] == NULL)
+ {
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher 0x%x unknown",
+ grub_le_to_cpu16 (header.alg));
+ return NULL;
+ }
+
+ ciphername = algorithms[grub_le_to_cpu16 (header.alg)];
+ ciph = grub_crypto_lookup_cipher_by_name (ciphername);
+ if (!ciph)
+ {
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "Cipher %s isn't available",
+ ciphername);
+ return NULL;
+ }
+
+ /* Configure the cipher used for the bulk data. */
+ cipher = grub_crypto_cipher_open (ciph);
+ if (!cipher)
+ return NULL;
+
+ if (grub_le_to_cpu16 (header.alg) == 0x16)
+ {
+ secondary_cipher = grub_crypto_cipher_open (ciph);
+ if (!secondary_cipher)
+ {
+ grub_crypto_cipher_close (cipher);
+ return NULL;
+ }
+
+ }
+
+ if (grub_le_to_cpu16 (header.keylen) > 1024)
+ {
+ grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid keysize %d",
+ grub_le_to_cpu16 (header.keylen));
+ grub_crypto_cipher_close (cipher);
+ grub_crypto_cipher_close (secondary_cipher);
+ return NULL;
+ }
+
+ newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+ if (!newdev)
+ {
+ grub_crypto_cipher_close (cipher);
+ grub_crypto_cipher_close (secondary_cipher);
+ return NULL;
+ }
+ newdev->cipher = cipher;
+ newdev->secondary_cipher = secondary_cipher;
+ newdev->offset_sectors = 0;
+ newdev->source_disk = NULL;
+ newdev->benbi_log = 0;
+ if (grub_le_to_cpu16 (header.alg) == 0x16)
+ {
+ newdev->mode = GRUB_CRYPTODISK_MODE_XTS;
+ newdev->mode_iv = GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64;
+ }
+ else
+ {
+ newdev->mode = GRUB_CRYPTODISK_MODE_CBC;
+ newdev->mode_iv = GRUB_CRYPTODISK_MODE_IV_BYTECOUNT64_HASH;
+ }
+ newdev->essiv_cipher = NULL;
+ newdev->essiv_hash = NULL;
+ newdev->hash = GRUB_MD_SHA512;
+ newdev->iv_hash = GRUB_MD_SHA256;
+
+ for (newdev->log_sector_size = 0;
+ (1U << newdev->log_sector_size) < grub_le_to_cpu32 (header.sector_size);
+ newdev->log_sector_size++);
+
+ if (grub_le_to_cpu32 (header.version) >= 5)
+ {
+ newdev->rekey = geli_rekey;
+ newdev->rekey_shift = 20;
+ }
+
+ newdev->modname = "geli";
+
+ newdev->total_sectors = grub_disk_native_sectors (disk) - 1;
+ grub_memcpy (newdev->uuid, uuid, sizeof (newdev->uuid));
+ COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= 32 * 2 + 1);
+ return newdev;
+}
+
+static grub_err_t
+recover_key (grub_disk_t source, grub_cryptodisk_t dev)
+{
+ grub_size_t keysize;
+ grub_uint8_t digest[GRUB_CRYPTO_MAX_MDLEN];
+ grub_uint8_t geomkey[GRUB_CRYPTO_MAX_MDLEN];
+ grub_uint8_t verify_key[GRUB_CRYPTO_MAX_MDLEN];
+ grub_uint8_t zero[GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE];
+ grub_uint8_t geli_cipher_key[64];
+ char passphrase[MAX_PASSPHRASE] = "";
+ unsigned i;
+ gcry_err_code_t gcry_err;
+ struct grub_geli_phdr header;
+ char *tmp;
+ grub_disk_addr_t sector;
+ grub_err_t err;
+
+ if (dev->cipher->cipher->blocksize > GRUB_CRYPTO_MAX_CIPHER_BLOCKSIZE)
+ return grub_error (GRUB_ERR_BUG, "cipher block is too long");
+
+ if (dev->hash->mdlen > GRUB_CRYPTO_MAX_MDLEN)
+ return grub_error (GRUB_ERR_BUG, "mdlen is too long");
+
+ sector = grub_disk_native_sectors (source);
+ if (sector == GRUB_DISK_SIZE_UNKNOWN || sector == 0)
+ return grub_error (GRUB_ERR_BUG, "not a geli");
+
+ /* Read the GELI header. */
+ err = grub_disk_read (source, sector - 1, 0, sizeof (header), &header);
+ if (err)
+ return err;
+
+ keysize = grub_le_to_cpu16 (header.keylen) / GRUB_CHAR_BIT;
+ grub_memset (zero, 0, sizeof (zero));
+
+ grub_puts_ (N_("Attempting to decrypt master key..."));
+
+ /* Get the passphrase from the user. */
+ tmp = NULL;
+ if (source->partition)
+ tmp = grub_partition_get_name (source->partition);
+ grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
+ source->partition ? "," : "", tmp ? : "",
+ dev->uuid);
+ grub_free (tmp);
+ if (!grub_password_get (passphrase, MAX_PASSPHRASE))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+
+ /* Calculate the PBKDF2 of the user supplied passphrase. */
+ if (grub_le_to_cpu32 (header.niter) != 0)
+ {
+ grub_uint8_t pbkdf_key[64];
+ gcry_err = grub_crypto_pbkdf2 (dev->hash, (grub_uint8_t *) passphrase,
+ grub_strlen (passphrase),
+ header.salt,
+ sizeof (header.salt),
+ grub_le_to_cpu32 (header.niter),
+ pbkdf_key, sizeof (pbkdf_key));
+
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ gcry_err = grub_crypto_hmac_buffer (dev->hash, NULL, 0, pbkdf_key,
+ sizeof (pbkdf_key), geomkey);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+ }
+ else
+ {
+ struct grub_crypto_hmac_handle *hnd;
+
+ hnd = grub_crypto_hmac_init (dev->hash, NULL, 0);
+ if (!hnd)
+ return grub_crypto_gcry_error (GPG_ERR_OUT_OF_MEMORY);
+
+ grub_crypto_hmac_write (hnd, header.salt, sizeof (header.salt));
+ grub_crypto_hmac_write (hnd, passphrase, grub_strlen (passphrase));
+
+ gcry_err = grub_crypto_hmac_fini (hnd, geomkey);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ gcry_err = grub_crypto_hmac_buffer (dev->hash, geomkey,
+ dev->hash->mdlen, "\1", 1, digest);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ gcry_err = grub_crypto_hmac_buffer (dev->hash, geomkey,
+ dev->hash->mdlen, "\0", 1, verify_key);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ grub_dprintf ("geli", "keylen = %" PRIuGRUB_SIZE "\n", keysize);
+
+ /* Try to recover master key from each active keyslot. */
+ for (i = 0; i < ARRAY_SIZE (header.keys); i++)
+ {
+ struct grub_geli_key candidate_key;
+ grub_uint8_t key_hmac[GRUB_CRYPTO_MAX_MDLEN];
+
+ /* Check if keyslot is enabled. */
+ if (! (header.keys_used & (1 << i)))
+ continue;
+
+ grub_dprintf ("geli", "Trying keyslot %d\n", i);
+
+ gcry_err = grub_crypto_cipher_set_key (dev->cipher,
+ digest, keysize);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ gcry_err = grub_crypto_cbc_decrypt (dev->cipher, &candidate_key,
+ &header.keys[i],
+ sizeof (candidate_key),
+ zero);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ gcry_err = grub_crypto_hmac_buffer (dev->hash, verify_key,
+ dev->hash->mdlen,
+ &candidate_key,
+ (sizeof (candidate_key)
+ - sizeof (candidate_key.hmac)),
+ key_hmac);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+
+ if (grub_memcmp (candidate_key.hmac, key_hmac, dev->hash->mdlen) != 0)
+ continue;
+ grub_printf_ (N_("Slot %d opened\n"), i);
+
+ if (grub_le_to_cpu32 (header.version) >= 7)
+ {
+ /* GELI >=7 uses the cipher_key */
+ grub_memcpy (geli_cipher_key, candidate_key.cipher_key,
+ sizeof (candidate_key.cipher_key));
+ }
+ else
+ {
+ /* GELI <=6 uses the iv_key */
+ grub_memcpy (geli_cipher_key, candidate_key.iv_key,
+ sizeof (candidate_key.iv_key));
+ }
+
+ /* Set the master key. */
+ if (!dev->rekey)
+ {
+ grub_size_t real_keysize = keysize;
+ if (grub_le_to_cpu16 (header.alg) == 0x16)
+ real_keysize *= 2;
+ gcry_err = grub_cryptodisk_setkey (dev, candidate_key.cipher_key,
+ real_keysize);
+ if (gcry_err)
+ return grub_crypto_gcry_error (gcry_err);
+ }
+ else
+ {
+ grub_size_t real_keysize = keysize;
+ if (grub_le_to_cpu16 (header.alg) == 0x16)
+ real_keysize *= 2;
+
+ grub_memcpy (dev->rekey_key, geli_cipher_key,
+ sizeof (geli_cipher_key));
+ dev->rekey_derived_size = real_keysize;
+ dev->last_rekey = -1;
+ COMPILE_TIME_ASSERT (sizeof (dev->rekey_key)
+ >= sizeof (geli_cipher_key));
+ }
+
+ dev->iv_prefix_len = sizeof (candidate_key.iv_key);
+ grub_memcpy (dev->iv_prefix, candidate_key.iv_key,
+ sizeof (candidate_key.iv_key));
+
+ COMPILE_TIME_ASSERT (sizeof (dev->iv_prefix) >= sizeof (candidate_key.iv_key));
+
+ return GRUB_ERR_NONE;
+ }
+
+ return GRUB_ACCESS_DENIED;
+}
+
+struct grub_cryptodisk_dev geli_crypto = {
+ .scan = configure_ciphers,
+ .recover_key = recover_key
+};
+
+GRUB_MOD_INIT (geli)
+{
+ grub_cryptodisk_dev_register (&geli_crypto);
+}
+
+GRUB_MOD_FINI (geli)
+{
+ grub_cryptodisk_dev_unregister (&geli_crypto);
+}
diff --git a/grub-core/disk/host.c b/grub-core/disk/host.c
new file mode 100644
index 0000000..c151d22
--- /dev/null
+++ b/grub-core/disk/host.c
@@ -0,0 +1,103 @@
+/* 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 <config.h>
+#include <config-util.h>
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/misc.h>
+#include <grub/emu/hostdisk.h>
+
+int grub_disk_host_i_want_a_reference;
+
+static int
+grub_host_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ if (hook ("host", hook_data))
+ 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 = 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,
+ .disk_iterate = grub_host_iterate,
+ .disk_open = grub_host_open,
+ .disk_close = grub_host_close,
+ .disk_read = grub_host_read,
+ .disk_write = grub_host_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(host)
+{
+ grub_disk_dev_register (&grub_host_dev);
+}
+
+GRUB_MOD_FINI(host)
+{
+ grub_disk_dev_unregister (&grub_host_dev);
+}
diff --git a/grub-core/disk/i386/pc/biosdisk.c b/grub-core/disk/i386/pc/biosdisk.c
new file mode 100644
index 0000000..8ca250c
--- /dev/null
+++ b/grub-core/disk/i386/pc/biosdisk.c
@@ -0,0 +1,688 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/machine/biosdisk.h>
+#include <grub/machine/kernel.h>
+#include <grub/machine/memory.h>
+#include <grub/machine/int.h>
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/err.h>
+#include <grub/term.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static int cd_drive = 0;
+static int grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap);
+
+static int grub_biosdisk_get_num_floppies (void)
+{
+ struct grub_bios_int_registers regs;
+ int drive;
+
+ /* reset the disk system first */
+ regs.eax = 0;
+ regs.edx = 0;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+
+ for (drive = 0; drive < 2; drive++)
+ {
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT | GRUB_CPU_INT_FLAGS_CARRY;
+ regs.edx = drive;
+
+ /* call GET DISK TYPE */
+ regs.eax = 0x1500;
+ grub_bios_interrupt (0x13, &regs);
+ if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
+ break;
+
+ /* check if this drive exists */
+ if (!(regs.eax & 0x300))
+ break;
+ }
+
+ return drive;
+}
+
+/*
+ * Call IBM/MS INT13 Extensions (int 13 %ah=AH) for DRIVE. DAP
+ * is passed for disk address packet. If an error occurs, return
+ * non-zero, otherwise zero.
+ */
+
+static int
+grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap)
+{
+ struct grub_bios_int_registers regs;
+ regs.eax = ah << 8;
+ /* compute the address of disk_address_packet */
+ regs.ds = (((grub_addr_t) dap) & 0xffff0000) >> 4;
+ regs.esi = (((grub_addr_t) dap) & 0xffff);
+ regs.edx = drive;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+ return (regs.eax >> 8) & 0xff;
+}
+
+/*
+ * Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write
+ * NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs,
+ * return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_rw_standard (int ah, int drive, int coff, int hoff,
+ int soff, int nsec, int segment)
+{
+ int ret, i;
+
+ /* Try 3 times. */
+ for (i = 0; i < 3; i++)
+ {
+ struct grub_bios_int_registers regs;
+
+ /* set up CHS information */
+ /* set %ch to low eight bits of cylinder */
+ regs.ecx = (coff << 8) & 0xff00;
+ /* set bits 6-7 of %cl to high two bits of cylinder */
+ regs.ecx |= (coff >> 2) & 0xc0;
+ /* set bits 0-5 of %cl to sector */
+ regs.ecx |= soff & 0x3f;
+
+ /* set %dh to head and %dl to drive */
+ regs.edx = (drive & 0xff) | ((hoff << 8) & 0xff00);
+ /* set %ah to AH */
+ regs.eax = (ah << 8) & 0xff00;
+ /* set %al to NSEC */
+ regs.eax |= nsec & 0xff;
+
+ regs.ebx = 0;
+ regs.es = segment;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+
+ grub_bios_interrupt (0x13, &regs);
+ /* check if successful */
+ if (!(regs.flags & GRUB_CPU_INT_FLAGS_CARRY))
+ return 0;
+
+ /* save return value */
+ ret = regs.eax >> 8;
+
+ /* if fail, reset the disk system */
+ regs.eax = 0;
+ regs.edx = (drive & 0xff);
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+ }
+ return ret;
+}
+
+/*
+ * Check if LBA is supported for DRIVE. If it is supported, then return
+ * the major version of extensions, otherwise zero.
+ */
+static int
+grub_biosdisk_check_int13_extensions (int drive)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.edx = drive & 0xff;
+ regs.eax = 0x4100;
+ regs.ebx = 0x55aa;
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY)
+ return 0;
+
+ if ((regs.ebx & 0xffff) != 0xaa55)
+ return 0;
+
+ /* check if AH=0x42 is supported */
+ if (!(regs.ecx & 1))
+ return 0;
+
+ return (regs.eax >> 8) & 0xff;
+}
+
+/*
+ * Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an
+ * error occurs, then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_diskinfo_standard (int drive,
+ unsigned long *cylinders,
+ unsigned long *heads,
+ unsigned long *sectors)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.eax = 0x0800;
+ regs.edx = drive & 0xff;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ /* Check if unsuccessful. Ignore return value if carry isn't set to
+ workaround some buggy BIOSes. */
+ if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
+ return (regs.eax & 0xff00) >> 8;
+
+ /* bogus BIOSes may not return an error number */
+ /* 0 sectors means no disk */
+ if (!(regs.ecx & 0x3f))
+ /* XXX 0x60 is one of the unused error numbers */
+ return 0x60;
+
+ /* the number of heads is counted from zero */
+ *heads = ((regs.edx >> 8) & 0xff) + 1;
+ *cylinders = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x0300)) + 1;
+ *sectors = regs.ecx & 0x3f;
+ return 0;
+}
+
+static int
+grub_biosdisk_get_diskinfo_real (int drive, void *drp, grub_uint16_t ax)
+{
+ struct grub_bios_int_registers regs;
+
+ regs.eax = ax;
+
+ /* compute the address of drive parameters */
+ regs.esi = ((grub_addr_t) drp) & 0xf;
+ regs.ds = ((grub_addr_t) drp) >> 4;
+ regs.edx = drive & 0xff;
+
+ regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT;
+ grub_bios_interrupt (0x13, &regs);
+
+ /* Check if unsuccessful. Ignore return value if carry isn't set to
+ workaround some buggy BIOSes. */
+ if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0))
+ return (regs.eax & 0xff00) >> 8;
+
+ return 0;
+}
+
+/*
+ * Return the cdrom information of DRIVE in CDRP. If an error occurs,
+ * then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_cdinfo_int13_extensions (int drive, void *cdrp)
+{
+ return grub_biosdisk_get_diskinfo_real (drive, cdrp, 0x4b01);
+}
+
+/*
+ * Return the geometry of DRIVE in a drive parameters, DRP. If an error
+ * occurs, then return non-zero, otherwise zero.
+ */
+static int
+grub_biosdisk_get_diskinfo_int13_extensions (int drive, void *drp)
+{
+ return grub_biosdisk_get_diskinfo_real (drive, drp, 0x4800);
+}
+
+static int
+grub_biosdisk_get_drive (const char *name)
+{
+ unsigned long drive;
+
+ if (name[0] == 'c' && name[1] == 'd' && name[2] == 0 && cd_drive)
+ return cd_drive;
+
+ if ((name[0] != 'f' && name[0] != 'h') || name[1] != 'd')
+ goto fail;
+
+ drive = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+
+ if (name[0] == 'h')
+ drive += 0x80;
+
+ return (int) drive ;
+
+ fail:
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a biosdisk");
+ return -1;
+}
+
+static int
+grub_biosdisk_call_hook (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ int drive)
+{
+ char name[10];
+
+ if (cd_drive && drive == cd_drive)
+ return hook ("cd", hook_data);
+
+ grub_snprintf (name, sizeof (name),
+ (drive & 0x80) ? "hd%d" : "fd%d", drive & (~0x80));
+ return hook (name, hook_data);
+}
+
+static int
+grub_biosdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ int num_floppies;
+ int drive;
+
+ /* For hard disks, attempt to read the MBR. */
+ switch (pull)
+ {
+ case GRUB_DISK_PULL_NONE:
+ for (drive = 0x80; drive < 0x90; drive++)
+ {
+ if (grub_biosdisk_rw_standard (0x02, drive, 0, 0, 1, 1,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG) != 0)
+ {
+ grub_dprintf ("disk", "Read error when probing drive 0x%2x\n", drive);
+ break;
+ }
+
+ if (grub_biosdisk_call_hook (hook, hook_data, drive))
+ return 1;
+ }
+ return 0;
+
+ case GRUB_DISK_PULL_REMOVABLE:
+ if (cd_drive)
+ {
+ if (grub_biosdisk_call_hook (hook, hook_data, cd_drive))
+ return 1;
+ }
+
+ /* For floppy disks, we can get the number safely. */
+ num_floppies = grub_biosdisk_get_num_floppies ();
+ for (drive = 0; drive < num_floppies; drive++)
+ if (grub_biosdisk_call_hook (hook, hook_data, drive))
+ return 1;
+ return 0;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+static grub_err_t
+grub_biosdisk_open (const char *name, grub_disk_t disk)
+{
+ grub_uint64_t total_sectors = 0;
+ int drive;
+ struct grub_biosdisk_data *data;
+
+ drive = grub_biosdisk_get_drive (name);
+ if (drive < 0)
+ return grub_errno;
+
+ disk->id = drive;
+
+ data = (struct grub_biosdisk_data *) grub_zalloc (sizeof (*data));
+ if (! data)
+ return grub_errno;
+
+ data->drive = drive;
+
+ if ((cd_drive) && (drive == cd_drive))
+ {
+ data->flags = GRUB_BIOSDISK_FLAG_LBA | GRUB_BIOSDISK_FLAG_CDROM;
+ data->sectors = 8;
+ disk->log_sector_size = 11;
+ /* TODO: get the correct size. */
+ total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ }
+ else
+ {
+ /* HDD */
+ int version;
+
+ disk->log_sector_size = 9;
+
+ version = grub_biosdisk_check_int13_extensions (drive);
+ if (version)
+ {
+ struct grub_biosdisk_drp *drp
+ = (struct grub_biosdisk_drp *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+
+ /* Clear out the DRP. */
+ grub_memset (drp, 0, sizeof (*drp));
+ drp->size = sizeof (*drp);
+ if (! grub_biosdisk_get_diskinfo_int13_extensions (drive, drp))
+ {
+ data->flags = GRUB_BIOSDISK_FLAG_LBA;
+
+ if (drp->total_sectors)
+ total_sectors = drp->total_sectors;
+ else
+ /* Some buggy BIOSes doesn't return the total sectors
+ correctly but returns zero. So if it is zero, compute
+ it by C/H/S returned by the LBA BIOS call. */
+ total_sectors = ((grub_uint64_t) drp->cylinders)
+ * drp->heads * drp->sectors;
+ if (drp->bytes_per_sector
+ && !(drp->bytes_per_sector & (drp->bytes_per_sector - 1))
+ && drp->bytes_per_sector >= 512
+ && drp->bytes_per_sector <= 16384)
+ {
+ for (disk->log_sector_size = 0;
+ (1 << disk->log_sector_size) < drp->bytes_per_sector;
+ disk->log_sector_size++);
+ }
+ }
+ }
+ }
+
+ if (! (data->flags & GRUB_BIOSDISK_FLAG_CDROM))
+ {
+ if (grub_biosdisk_get_diskinfo_standard (drive,
+ &data->cylinders,
+ &data->heads,
+ &data->sectors) != 0)
+ {
+ if (total_sectors && (data->flags & GRUB_BIOSDISK_FLAG_LBA))
+ {
+ data->sectors = 63;
+ data->heads = 255;
+ data->cylinders
+ = grub_divmod64 (total_sectors
+ + data->heads * data->sectors - 1,
+ data->heads * data->sectors, 0);
+ }
+ else
+ {
+ grub_free (data);
+ return grub_error (GRUB_ERR_BAD_DEVICE, "%s cannot get C/H/S values", disk->name);
+ }
+ }
+
+ if (data->sectors == 0)
+ data->sectors = 63;
+ if (data->heads == 0)
+ data->heads = 255;
+
+ if (! total_sectors)
+ total_sectors = ((grub_uint64_t) data->cylinders)
+ * data->heads * data->sectors;
+ }
+
+ disk->total_sectors = total_sectors;
+ /* Limit the max to 0x7f because of Phoenix EDD. */
+ disk->max_agglomerate = 0x7f >> GRUB_DISK_CACHE_BITS;
+ COMPILE_TIME_ASSERT ((0x7f >> GRUB_DISK_CACHE_BITS
+ << (GRUB_DISK_SECTOR_BITS + GRUB_DISK_CACHE_BITS))
+ + sizeof (struct grub_biosdisk_dap)
+ < GRUB_MEMORY_MACHINE_SCRATCH_SIZE);
+
+ disk->data = data;
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_biosdisk_close (grub_disk_t disk)
+{
+ grub_free (disk->data);
+}
+
+/* For readability. */
+#define GRUB_BIOSDISK_READ 0
+#define GRUB_BIOSDISK_WRITE 1
+
+#define GRUB_BIOSDISK_CDROM_RETRY_COUNT 3
+
+static grub_err_t
+grub_biosdisk_rw (int cmd, grub_disk_t disk,
+ grub_disk_addr_t sector, grub_size_t size,
+ unsigned segment)
+{
+ struct grub_biosdisk_data *data = disk->data;
+
+ /* VirtualBox fails with sectors above 2T on CDs.
+ Since even BD-ROMS are never that big anyway, return error. */
+ if ((data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ && (sector >> 32))
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_LBA)
+ {
+ struct grub_biosdisk_dap *dap;
+
+ dap = (struct grub_biosdisk_dap *) (GRUB_MEMORY_MACHINE_SCRATCH_ADDR
+ + (data->sectors
+ << disk->log_sector_size));
+ dap->length = sizeof (*dap);
+ dap->reserved = 0;
+ dap->blocks = size;
+ dap->buffer = segment << 16; /* The format SEGMENT:ADDRESS. */
+ dap->block = sector;
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ {
+ int i;
+
+ if (cmd)
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("cannot write to CD-ROM"));
+
+ for (i = 0; i < GRUB_BIOSDISK_CDROM_RETRY_COUNT; i++)
+ if (! grub_biosdisk_rw_int13_extensions (0x42, data->drive, dap))
+ break;
+
+ if (i == GRUB_BIOSDISK_CDROM_RETRY_COUNT)
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ }
+ else
+ if (grub_biosdisk_rw_int13_extensions (cmd + 0x42, data->drive, dap))
+ {
+ /* Fall back to the CHS mode. */
+ data->flags &= ~GRUB_BIOSDISK_FLAG_LBA;
+ disk->total_sectors = data->cylinders * data->heads * data->sectors;
+ return grub_biosdisk_rw (cmd, disk, sector, size, segment);
+ }
+ }
+ else
+ {
+ unsigned coff, hoff, soff;
+ unsigned head;
+
+ /* It is impossible to reach over 8064 MiB (a bit less than LBA24) with
+ the traditional CHS access. */
+ if (sector >
+ 1024 /* cylinders */ *
+ 256 /* heads */ *
+ 63 /* spt */)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ soff = ((grub_uint32_t) sector) % data->sectors + 1;
+ head = ((grub_uint32_t) sector) / data->sectors;
+ hoff = head % data->heads;
+ coff = head / data->heads;
+
+ if (coff >= data->cylinders)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ N_("attempt to read or write outside of disk `%s'"),
+ disk->name);
+
+ if (grub_biosdisk_rw_standard (cmd + 0x02, data->drive,
+ coff, hoff, soff, size, segment))
+ {
+ switch (cmd)
+ {
+ case GRUB_BIOSDISK_READ:
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ case GRUB_BIOSDISK_WRITE:
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx "
+ "to `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ }
+ }
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+/* Return the number of sectors which can be read safely at a time. */
+static grub_size_t
+get_safe_sectors (grub_disk_t disk, grub_disk_addr_t sector)
+{
+ grub_size_t size;
+ grub_uint64_t offset;
+ struct grub_biosdisk_data *data = disk->data;
+ grub_uint32_t sectors = data->sectors;
+
+ /* OFFSET = SECTOR % SECTORS */
+ grub_divmod64 (sector, sectors, &offset);
+
+ size = sectors - offset;
+
+ return size;
+}
+
+static grub_err_t
+grub_biosdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ while (size)
+ {
+ grub_size_t len;
+
+ len = get_safe_sectors (disk, sector);
+
+ if (len > size)
+ len = size;
+
+ if (grub_biosdisk_rw (GRUB_BIOSDISK_READ, disk, sector, len,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG))
+ return grub_errno;
+
+ grub_memcpy (buf, (void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR,
+ len << disk->log_sector_size);
+
+ buf += len << disk->log_sector_size;
+ sector += len;
+ size -= len;
+ }
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_biosdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ struct grub_biosdisk_data *data = disk->data;
+
+ if (data->flags & GRUB_BIOSDISK_FLAG_CDROM)
+ return grub_error (GRUB_ERR_IO, N_("cannot write to CD-ROM"));
+
+ while (size)
+ {
+ grub_size_t len;
+
+ len = get_safe_sectors (disk, sector);
+ if (len > size)
+ len = size;
+
+ grub_memcpy ((void *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR, buf,
+ len << disk->log_sector_size);
+
+ if (grub_biosdisk_rw (GRUB_BIOSDISK_WRITE, disk, sector, len,
+ GRUB_MEMORY_MACHINE_SCRATCH_SEG))
+ return grub_errno;
+
+ buf += len << disk->log_sector_size;
+ sector += len;
+ size -= len;
+ }
+
+ return grub_errno;
+}
+
+static struct grub_disk_dev grub_biosdisk_dev =
+ {
+ .name = "biosdisk",
+ .id = GRUB_DISK_DEVICE_BIOSDISK_ID,
+ .disk_iterate = grub_biosdisk_iterate,
+ .disk_open = grub_biosdisk_open,
+ .disk_close = grub_biosdisk_close,
+ .disk_read = grub_biosdisk_read,
+ .disk_write = grub_biosdisk_write,
+ .next = 0
+ };
+
+static void
+grub_disk_biosdisk_fini (void)
+{
+ grub_disk_dev_unregister (&grub_biosdisk_dev);
+}
+
+GRUB_MOD_INIT(biosdisk)
+{
+ struct grub_biosdisk_cdrp *cdrp
+ = (struct grub_biosdisk_cdrp *) GRUB_MEMORY_MACHINE_SCRATCH_ADDR;
+ grub_uint8_t boot_drive;
+
+ if (grub_disk_firmware_is_tainted)
+ {
+ grub_puts_ (N_("Native disk drivers are in use. "
+ "Refusing to use firmware disk interface."));
+ return;
+ }
+ grub_disk_firmware_fini = grub_disk_biosdisk_fini;
+
+ grub_memset (cdrp, 0, sizeof (*cdrp));
+ cdrp->size = sizeof (*cdrp);
+ cdrp->media_type = 0xFF;
+ boot_drive = (grub_boot_device >> 24);
+ if ((! grub_biosdisk_get_cdinfo_int13_extensions (boot_drive, cdrp))
+ && ((cdrp->media_type & GRUB_BIOSDISK_CDTYPE_MASK)
+ == GRUB_BIOSDISK_CDTYPE_NO_EMUL))
+ cd_drive = cdrp->drive_no;
+ /* Since diskboot.S rejects devices over 0x90 it must be a CD booted with
+ cdboot.S
+ */
+ if (boot_drive >= 0x90)
+ cd_drive = boot_drive;
+
+ grub_disk_dev_register (&grub_biosdisk_dev);
+}
+
+GRUB_MOD_FINI(biosdisk)
+{
+ grub_disk_biosdisk_fini ();
+}
diff --git a/grub-core/disk/ieee1275/nand.c b/grub-core/disk/ieee1275/nand.c
new file mode 100644
index 0000000..bcf3a06
--- /dev/null
+++ b/grub-core/disk/ieee1275/nand.c
@@ -0,0 +1,242 @@
+/* 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>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_nand_data
+{
+ grub_ieee1275_ihandle_t handle;
+ grub_uint32_t block_size;
+};
+
+static int
+grub_nand_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ static int have_nand = -1;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ if (have_nand == -1)
+ {
+ struct grub_ieee1275_devalias alias;
+
+ have_nand = 0;
+ FOR_IEEE1275_DEVALIASES(alias)
+ if (grub_strcmp (alias.name, "nand") == 0)
+ {
+ have_nand = 1;
+ break;
+ }
+ grub_ieee1275_devalias_free (&alias);
+ }
+
+ if (have_nand)
+ return hook ("nand", hook_data);
+
+ return 0;
+}
+
+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;
+ const char *devname;
+ 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_memcmp (name, "nand/", sizeof ("nand/") - 1) == 0)
+ devname = name + sizeof ("nand/") - 1;
+ else if (grub_strcmp (name, "nand") == 0)
+ devname = name;
+ else
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a NAND device");
+
+ data = grub_malloc (sizeof (*data));
+ if (! data)
+ goto fail;
+
+ grub_ieee1275_open (devname, &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);
+ if (!data->block_size)
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "invalid block size");
+ goto fail;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 3);
+ args.method = (grub_ieee1275_cell_t) "size";
+ args.ihandle = dev_ihandle;
+ args.result = 1;
+
+ if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result))
+ {
+ grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't get disk size");
+ goto fail;
+ }
+
+ disk->total_sectors = args.size1;
+ disk->total_sectors <<= 32;
+ disk->total_sectors += args.size2;
+ disk->total_sectors >>= GRUB_DISK_SECTOR_BITS;
+
+ disk->id = dev_ihandle;
+
+ disk->data = data;
+
+ return 0;
+
+fail:
+ if (dev_ihandle)
+ grub_ieee1275_close (dev_ihandle);
+ grub_free (data);
+ return grub_errno;
+}
+
+static void
+grub_nand_close (grub_disk_t disk)
+{
+ grub_ieee1275_close (((struct grub_nand_data *) disk->data)->handle);
+ grub_free (disk->data);
+}
+
+static grub_err_t
+grub_nand_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ struct grub_nand_data *data = disk->data;
+ grub_size_t bsize, ofs;
+
+ struct read_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t ihandle;
+ grub_ieee1275_cell_t ofs;
+ grub_ieee1275_cell_t page;
+ grub_ieee1275_cell_t len;
+ grub_ieee1275_cell_t buf;
+ grub_ieee1275_cell_t result;
+ } args;
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 6, 1);
+ args.method = (grub_ieee1275_cell_t) "pio-read";
+ args.ihandle = data->handle;
+ args.buf = (grub_ieee1275_cell_t) buf;
+ args.page = (grub_ieee1275_cell_t) ((grub_size_t) sector / data->block_size);
+
+ ofs = ((grub_size_t) sector % data->block_size) << GRUB_DISK_SECTOR_BITS;
+ size <<= GRUB_DISK_SECTOR_BITS;
+ bsize = (data->block_size << GRUB_DISK_SECTOR_BITS);
+
+ do
+ {
+ grub_size_t len;
+
+ len = (ofs + size > bsize) ? (bsize - ofs) : size;
+
+ args.len = (grub_ieee1275_cell_t) len;
+ args.ofs = (grub_ieee1275_cell_t) ofs;
+ args.result = 1;
+
+ if ((IEEE1275_CALL_ENTRY_FN (&args) == -1) || (args.result))
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+
+ 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_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "nand write is not supported");
+}
+
+static struct grub_disk_dev grub_nand_dev =
+ {
+ .name = "nand",
+ .id = GRUB_DISK_DEVICE_NAND_ID,
+ .disk_iterate = grub_nand_iterate,
+ .disk_open = grub_nand_open,
+ .disk_close = grub_nand_close,
+ .disk_read = grub_nand_read,
+ .disk_write = grub_nand_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(nand)
+{
+ grub_disk_dev_register (&grub_nand_dev);
+}
+
+GRUB_MOD_FINI(nand)
+{
+ grub_disk_dev_unregister (&grub_nand_dev);
+}
diff --git a/grub-core/disk/ieee1275/obdisk.c b/grub-core/disk/ieee1275/obdisk.c
new file mode 100644
index 0000000..ec413c3
--- /dev/null
+++ b/grub-core/disk/ieee1275/obdisk.c
@@ -0,0 +1,1076 @@
+/* obdisk.c - Open Boot disk access. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2019 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/env.h>
+#include <grub/i18n.h>
+#include <grub/kernel.h>
+#include <grub/list.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/scsicmd.h>
+#include <grub/time.h>
+#include <grub/ieee1275/ieee1275.h>
+#include <grub/ieee1275/obdisk.h>
+
+#define IEEE1275_DEV "ieee1275/"
+#define IEEE1275_DISK_ALIAS "/disk@"
+
+struct disk_dev
+{
+ struct disk_dev *next;
+ struct disk_dev **prev;
+ char *name;
+ char *raw_name;
+ char *grub_devpath;
+ char *grub_alias_devpath;
+ grub_ieee1275_ihandle_t ihandle;
+ grub_uint32_t block_size;
+ grub_uint64_t num_blocks;
+ unsigned int log_sector_size;
+ grub_uint32_t opened;
+ grub_uint32_t valid;
+ grub_uint32_t boot_dev;
+};
+
+struct parent_dev
+{
+ struct parent_dev *next;
+ struct parent_dev **prev;
+ char *name;
+ char *type;
+ grub_ieee1275_ihandle_t ihandle;
+ grub_uint32_t address_cells;
+};
+
+static struct grub_scsi_test_unit_ready tur =
+{
+ .opcode = grub_scsi_cmd_test_unit_ready,
+ .lun = 0,
+ .reserved1 = 0,
+ .reserved2 = 0,
+ .reserved3 = 0,
+ .control = 0,
+};
+
+static int disks_enumerated;
+static struct disk_dev *disk_devs;
+static struct parent_dev *parent_devs;
+
+static const char *block_blacklist[] = {
+ /* Requires additional work in grub before being able to be used. */
+ "/iscsi-hba",
+ /* This block device should never be used by grub. */
+ "/reboot-memory@0",
+ 0
+};
+
+#define STRCMP(a, b) ((a) && (b) && (grub_strcmp (a, b) == 0))
+
+static char *
+strip_ob_partition (char *path)
+{
+ char *sptr;
+
+ sptr = grub_strstr (path, ":");
+
+ if (sptr != NULL)
+ *sptr = '\0';
+
+ return path;
+}
+
+static void
+escape_commas (const char *src, char *dest)
+{
+ const char *iptr;
+
+ for (iptr = src; *iptr; )
+ {
+ if (*iptr == ',')
+ *dest++ ='\\';
+
+ *dest++ = *iptr++;
+ }
+
+ *dest = '\0';
+}
+
+static int
+count_commas (const char *src)
+{
+ int count = 0;
+
+ for ( ; *src; src++)
+ if (*src == ',')
+ count++;
+
+ return count;
+}
+
+
+static char *
+decode_grub_devname (const char *name)
+{
+ char *devpath = grub_malloc (grub_strlen (name) + 1);
+ char *p, c;
+
+ if (devpath == NULL)
+ return NULL;
+
+ /* Un-escape commas. */
+ p = devpath;
+ while ((c = *name++) != '\0')
+ {
+ if (c == '\\' && *name == ',')
+ {
+ *p++ = ',';
+ name++;
+ }
+ else
+ *p++ = c;
+ }
+
+ *p++ = '\0';
+
+ return devpath;
+}
+
+static char *
+encode_grub_devname (const char *path)
+{
+ char *encoding, *optr;
+
+ if (path == NULL)
+ return NULL;
+
+ encoding = grub_malloc (sizeof (IEEE1275_DEV) + count_commas (path) +
+ grub_strlen (path) + 1);
+
+ if (encoding == NULL)
+ {
+ grub_print_error ();
+ return NULL;
+ }
+
+ optr = grub_stpcpy (encoding, IEEE1275_DEV);
+ escape_commas (path, optr);
+ return encoding;
+}
+
+static char *
+get_parent_devname (const char *devname)
+{
+ char *parent, *pptr;
+
+ parent = grub_strdup (devname);
+
+ if (parent == NULL)
+ {
+ grub_print_error ();
+ return NULL;
+ }
+
+ pptr = grub_strstr (parent, IEEE1275_DISK_ALIAS);
+
+ if (pptr != NULL)
+ *pptr = '\0';
+
+ return parent;
+}
+
+static void
+free_parent_dev (struct parent_dev *parent)
+{
+ if (parent != NULL)
+ {
+ grub_free (parent->name);
+ grub_free (parent->type);
+ grub_free (parent);
+ }
+}
+
+static struct parent_dev *
+init_parent (const char *parent)
+{
+ struct parent_dev *op;
+
+ op = grub_zalloc (sizeof (struct parent_dev));
+
+ if (op == NULL)
+ {
+ grub_print_error ();
+ return NULL;
+ }
+
+ op->name = grub_strdup (parent);
+ op->type = grub_malloc (IEEE1275_MAX_PROP_LEN);
+
+ if ((op->name == NULL) || (op->type == NULL))
+ {
+ grub_print_error ();
+ free_parent_dev (op);
+ return NULL;
+ }
+
+ return op;
+}
+
+static struct parent_dev *
+open_new_parent (const char *parent)
+{
+ struct parent_dev *op = init_parent(parent);
+ grub_ieee1275_ihandle_t ihandle;
+ grub_ieee1275_phandle_t phandle;
+ grub_uint32_t address_cells = 2;
+
+ if (op == NULL)
+ return NULL;
+
+ grub_ieee1275_open (parent, &ihandle);
+
+ if (ihandle == 0)
+ {
+ grub_error (GRUB_ERR_BAD_DEVICE, "unable to open %s", parent);
+ grub_print_error ();
+ free_parent_dev (op);
+ return NULL;
+ }
+
+ if (grub_ieee1275_instance_to_package (ihandle, &phandle))
+ {
+ grub_error (GRUB_ERR_BAD_DEVICE, "unable to get parent %s", parent);
+ grub_print_error ();
+ free_parent_dev (op);
+ return NULL;
+ }
+
+ /*
+ * IEEE Std 1275-1994 page 110: A missing "address-cells" property
+ * signifies that the number of address cells is two. So ignore on error.
+ */
+ if (grub_ieee1275_get_integer_property (phandle, "#address-cells",
+ &address_cells,
+ sizeof (address_cells), 0) != 0)
+ address_cells = 2;
+
+ grub_ieee1275_get_property (phandle, "device_type", op->type,
+ IEEE1275_MAX_PROP_LEN, NULL);
+
+ op->ihandle = ihandle;
+ op->address_cells = address_cells;
+ return op;
+}
+
+static struct parent_dev *
+open_parent (const char *parent)
+{
+ struct parent_dev *op;
+
+ op = grub_named_list_find (GRUB_AS_NAMED_LIST (parent_devs), parent);
+
+ if (op == NULL)
+ {
+ op = open_new_parent (parent);
+
+ if (op != NULL)
+ grub_list_push (GRUB_AS_LIST_P (&parent_devs), GRUB_AS_LIST (op));
+ }
+
+ return op;
+}
+
+static void
+display_parents (void)
+{
+ struct parent_dev *parent;
+
+ grub_printf ("-------------------- PARENTS --------------------\n");
+
+ FOR_LIST_ELEMENTS (parent, parent_devs)
+ {
+ grub_printf ("name: %s\n", parent->name);
+ grub_printf ("type: %s\n", parent->type);
+ grub_printf ("address_cells %x\n", parent->address_cells);
+ }
+
+ grub_printf ("-------------------------------------------------\n");
+}
+
+static char *
+canonicalise_4cell_ua (grub_ieee1275_ihandle_t ihandle, char *unit_address)
+{
+ grub_uint32_t phy_lo, phy_hi, lun_lo, lun_hi;
+ int valid_phy = 0;
+ grub_size_t size;
+ char *canon = NULL;
+
+ valid_phy = grub_ieee1275_decode_unit4 (ihandle, unit_address,
+ grub_strlen (unit_address), &phy_lo,
+ &phy_hi, &lun_lo, &lun_hi);
+
+ if ((valid_phy == 0) && (phy_hi != 0xffffffff))
+ canon = grub_ieee1275_encode_uint4 (ihandle, phy_lo, phy_hi,
+ lun_lo, lun_hi, &size);
+
+ return canon;
+}
+
+static char *
+canonicalise_disk (const char *devname)
+{
+ char *canon, *parent;
+ struct parent_dev *op;
+
+ canon = grub_ieee1275_canonicalise_devname (devname);
+
+ if (canon == NULL)
+ {
+ /* This should not happen. */
+ grub_error (GRUB_ERR_BAD_DEVICE, "canonicalise devname failed");
+ grub_print_error ();
+ return NULL;
+ }
+
+ /* Don't try to open the parent of a virtual device. */
+ if (grub_strstr (canon, "virtual-devices"))
+ return canon;
+
+ parent = get_parent_devname (canon);
+
+ if (parent == NULL)
+ return NULL;
+
+ op = open_parent (parent);
+
+ /*
+ * Devices with 4 address cells can have many different types of addressing
+ * (phy, wwn, and target lun). Use the parents encode-unit / decode-unit
+ * to find the true canonical name.
+ */
+ if ((op) && (op->address_cells == 4))
+ {
+ char *unit_address, *real_unit_address, *real_canon;
+ grub_size_t real_unit_str_len;
+
+ unit_address = grub_strstr (canon, IEEE1275_DISK_ALIAS);
+ unit_address += grub_strlen (IEEE1275_DISK_ALIAS);
+
+ if (unit_address == NULL)
+ {
+ /*
+ * This should not be possible, but return the canonical name for
+ * the non-disk block device.
+ */
+ grub_free (parent);
+ return (canon);
+ }
+
+ real_unit_address = canonicalise_4cell_ua (op->ihandle, unit_address);
+
+ if (real_unit_address == NULL)
+ {
+ /*
+ * This is not an error, since this function could be called with a devalias
+ * containing a drive that isn't installed in the system.
+ */
+ grub_free (parent);
+ return NULL;
+ }
+
+ real_unit_str_len = grub_strlen (op->name) + sizeof (IEEE1275_DISK_ALIAS)
+ + grub_strlen (real_unit_address);
+
+ real_canon = grub_malloc (real_unit_str_len);
+
+ grub_snprintf (real_canon, real_unit_str_len, "%s/disk@%s",
+ op->name, real_unit_address);
+
+ grub_free (canon);
+ canon = real_canon;
+ }
+
+ grub_free (parent);
+ return (canon);
+}
+
+static struct disk_dev *
+add_canon_disk (const char *cname)
+{
+ struct disk_dev *dev;
+
+ dev = grub_zalloc (sizeof (struct disk_dev));
+
+ if (dev == NULL)
+ goto failed;
+
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_RAW_DEVNAMES))
+ {
+ /*
+ * Append :nolabel to the end of all SPARC disks.
+ * nolabel is mutually exclusive with all other
+ * arguments and allows a client program to open
+ * the entire (raw) disk. Any disk label is ignored.
+ */
+ dev->raw_name = grub_malloc (grub_strlen (cname) + sizeof (":nolabel"));
+
+ if (dev->raw_name == NULL)
+ goto failed;
+
+ grub_snprintf (dev->raw_name, grub_strlen (cname) + sizeof (":nolabel"),
+ "%s:nolabel", cname);
+ }
+
+ /*
+ * Don't use grub_ieee1275_encode_devname here, the devpath in grub.cfg doesn't
+ * understand device aliases, which the layer above sometimes sends us.
+ */
+ dev->grub_devpath = encode_grub_devname(cname);
+
+ if (dev->grub_devpath == NULL)
+ goto failed;
+
+ dev->name = grub_strdup (cname);
+
+ if (dev->name == NULL)
+ goto failed;
+
+ dev->valid = 1;
+ grub_list_push (GRUB_AS_LIST_P (&disk_devs), GRUB_AS_LIST (dev));
+ return dev;
+
+ failed:
+ grub_print_error ();
+
+ if (dev != NULL)
+ {
+ grub_free (dev->name);
+ grub_free (dev->grub_devpath);
+ grub_free (dev->raw_name);
+ }
+
+ grub_free (dev);
+ return NULL;
+}
+
+static grub_err_t
+add_disk (const char *path)
+{
+ grub_err_t ret = GRUB_ERR_NONE;
+ struct disk_dev *dev;
+ char *canon;
+
+ canon = canonicalise_disk (path);
+ dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon);
+
+ if ((canon != NULL) && (dev == NULL))
+ {
+ struct disk_dev *ob_device;
+
+ ob_device = add_canon_disk (canon);
+
+ if (ob_device == NULL)
+ ret = grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure to add disk");
+ }
+ else if (dev != NULL)
+ dev->valid = 1;
+
+ grub_free (canon);
+ return (ret);
+}
+
+static grub_err_t
+grub_obdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *dest)
+{
+ grub_err_t ret = GRUB_ERR_NONE;
+ struct disk_dev *dev;
+ unsigned long long pos;
+ grub_ssize_t result = 0;
+
+ if (disk->data == NULL)
+ return grub_error (GRUB_ERR_BAD_DEVICE, "invalid disk data");
+
+ dev = (struct disk_dev *)disk->data;
+ pos = sector << disk->log_sector_size;
+ grub_ieee1275_seek (dev->ihandle, pos, &result);
+
+ if (result < 0)
+ {
+ dev->opened = 0;
+ return grub_error (GRUB_ERR_READ_ERROR, "seek error, can't seek block %llu",
+ (long long) sector);
+ }
+
+ grub_ieee1275_read (dev->ihandle, dest, size << disk->log_sector_size,
+ &result);
+
+ if (result != (grub_ssize_t) (size << disk->log_sector_size))
+ {
+ dev->opened = 0;
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+ }
+ return ret;
+}
+
+static void
+grub_obdisk_close (grub_disk_t disk)
+{
+ grub_memset (disk, 0, sizeof (*disk));
+}
+
+static void
+scan_usb_disk (const char *parent)
+{
+ struct parent_dev *op;
+ grub_ssize_t result;
+
+ op = open_parent (parent);
+
+ if (op == NULL)
+ {
+ grub_error (GRUB_ERR_BAD_DEVICE, "unable to open %s", parent);
+ grub_print_error ();
+ return;
+ }
+
+ if ((grub_ieee1275_set_address (op->ihandle, 0, 0) == 0) &&
+ (grub_ieee1275_no_data_command (op->ihandle, &tur, &result) == 0) &&
+ (result == 0))
+ {
+ char *buf;
+
+ buf = grub_malloc (IEEE1275_MAX_PATH_LEN);
+
+ if (buf == NULL)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure");
+ grub_print_error ();
+ return;
+ }
+
+ grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@0", parent);
+ add_disk (buf);
+ grub_free (buf);
+ }
+}
+
+static void
+scan_nvme_disk (const char *path)
+{
+ char *buf;
+
+ buf = grub_malloc (IEEE1275_MAX_PATH_LEN);
+
+ if (buf == NULL)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure");
+ grub_print_error ();
+ return;
+ }
+
+ grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@1", path);
+ add_disk (buf);
+ grub_free (buf);
+}
+
+static void
+scan_sparc_sas_2cell (struct parent_dev *op)
+{
+ grub_ssize_t result;
+ grub_uint8_t tgt;
+ char *buf;
+
+ buf = grub_malloc (IEEE1275_MAX_PATH_LEN);
+
+ if (buf == NULL)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure");
+ grub_print_error ();
+ return;
+ }
+
+ for (tgt = 0; tgt < 0xf; tgt++)
+ {
+
+ if ((grub_ieee1275_set_address(op->ihandle, tgt, 0) == 0) &&
+ (grub_ieee1275_no_data_command (op->ihandle, &tur, &result) == 0) &&
+ (result == 0))
+ {
+
+ grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@%"
+ PRIxGRUB_UINT32_T, op->name, tgt);
+
+ add_disk (buf);
+ }
+ }
+}
+
+static void
+scan_sparc_sas_4cell (struct parent_dev *op)
+{
+ grub_uint16_t exp;
+ grub_uint8_t phy;
+ char *buf;
+
+ buf = grub_malloc (IEEE1275_MAX_PATH_LEN);
+
+ if (buf == NULL)
+ {
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, "disk scan failure");
+ grub_print_error ();
+ return;
+ }
+
+ /*
+ * Cycle thru the potential for dual ported SAS disks
+ * behind a SAS expander.
+ */
+ for (exp = 0; exp <= 0x100; exp+=0x100)
+
+ /* The current limit is 32 disks on a phy. */
+ for (phy = 0; phy < 0x20; phy++)
+ {
+ char *canon = NULL;
+
+ grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "p%" PRIxGRUB_UINT32_T ",0",
+ exp | phy);
+
+ canon = canonicalise_4cell_ua (op->ihandle, buf);
+
+ if (canon != NULL)
+ {
+ grub_snprintf (buf, IEEE1275_MAX_PATH_LEN, "%s/disk@%s",
+ op->name, canon);
+
+ add_disk (buf);
+ grub_free (canon);
+ }
+ }
+
+ grub_free (buf);
+}
+
+static void
+scan_sparc_sas_disk (const char *parent)
+{
+ struct parent_dev *op;
+
+ op = open_parent (parent);
+
+ if ((op != NULL) && (op->address_cells == 4))
+ scan_sparc_sas_4cell (op);
+ else if ((op != NULL) && (op->address_cells == 2))
+ scan_sparc_sas_2cell (op);
+}
+
+static void
+iterate_devtree (const struct grub_ieee1275_devalias *alias)
+{
+ struct grub_ieee1275_devalias child;
+
+ if ((grub_strcmp (alias->type, "scsi-2") == 0) ||
+ (grub_strcmp (alias->type, "scsi-sas") == 0))
+ return scan_sparc_sas_disk (alias->path);
+
+ else if (grub_strcmp (alias->type, "nvme") == 0)
+ return scan_nvme_disk (alias->path);
+
+ else if (grub_strcmp (alias->type, "scsi-usb") == 0)
+ return scan_usb_disk (alias->path);
+
+ else if (grub_strcmp (alias->type, "block") == 0)
+ {
+ const char **bl = block_blacklist;
+
+ while (*bl != NULL)
+ {
+ if (grub_strstr (alias->path, *bl))
+ return;
+ bl++;
+ }
+
+ add_disk (alias->path);
+ return;
+ }
+
+ FOR_IEEE1275_DEVCHILDREN (alias->path, child)
+ iterate_devtree (&child);
+}
+
+static void
+enumerate_disks (void)
+{
+ struct grub_ieee1275_devalias alias;
+
+ FOR_IEEE1275_DEVCHILDREN("/", alias)
+ iterate_devtree (&alias);
+}
+
+static grub_err_t
+add_bootpath (void)
+{
+ struct disk_dev *ob_device;
+ grub_err_t ret = GRUB_ERR_NONE;
+ char *dev, *alias;
+ char *type;
+
+ dev = grub_ieee1275_get_boot_dev ();
+
+ if (dev == NULL)
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device");
+
+ type = grub_ieee1275_get_device_type (dev);
+
+ if (type == NULL)
+ {
+ grub_free (dev);
+ return grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device");
+ }
+
+ alias = NULL;
+
+ if (grub_strcmp (type, "network") != 0)
+ {
+ dev = strip_ob_partition (dev);
+ ob_device = add_canon_disk (dev);
+
+ if (ob_device == NULL)
+ ret = grub_error (GRUB_ERR_OUT_OF_MEMORY, "failure adding boot device");
+
+ ob_device->valid = 1;
+
+ alias = grub_ieee1275_get_devname (dev);
+
+ if (alias && grub_strcmp (alias, dev) != 0)
+ ob_device->grub_alias_devpath = grub_ieee1275_encode_devname (dev);
+
+ ob_device->boot_dev = 1;
+ }
+
+ grub_free (type);
+ grub_free (dev);
+ grub_free (alias);
+ return ret;
+}
+
+static void
+enumerate_aliases (void)
+{
+ struct grub_ieee1275_devalias alias;
+
+ /*
+ * Some block device aliases are not in canonical form
+ *
+ * For example:
+ *
+ * disk3 /pci@301/pci@1/scsi@0/disk@p3
+ * disk2 /pci@301/pci@1/scsi@0/disk@p2
+ * disk1 /pci@301/pci@1/scsi@0/disk@p1
+ * disk /pci@301/pci@1/scsi@0/disk@p0
+ * disk0 /pci@301/pci@1/scsi@0/disk@p0
+ *
+ * None of these devices are in canonical form.
+ *
+ * Also, just because there is a devalias, doesn't mean there is a disk
+ * at that location. And a valid boot block device doesn't have to have
+ * a devalias at all.
+ *
+ * At this point, all valid disks have been found in the system
+ * and devaliases that point to canonical names are stored in the
+ * disk_devs list already.
+ */
+ FOR_IEEE1275_DEVALIASES (alias)
+ {
+ struct disk_dev *dev;
+ char *canon;
+
+ if (grub_strcmp (alias.type, "block") != 0)
+ continue;
+
+ canon = canonicalise_disk (alias.name);
+
+ if (canon == NULL)
+ /* This is not an error, a devalias could point to a nonexistent disk. */
+ continue;
+
+ dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon);
+
+ if (dev != NULL)
+ {
+ /*
+ * If more than one alias points to the same device,
+ * remove the previous one unless it is the boot dev,
+ * since the upper level will use the first one. The reason
+ * all the others are redone is in the case of hot-plugging
+ * a disk. If the boot disk gets hot-plugged, it will come
+ * thru here with a different name without the boot_dev flag
+ * set.
+ */
+ if ((dev->boot_dev) && (dev->grub_alias_devpath))
+ continue;
+
+ grub_free (dev->grub_alias_devpath);
+ dev->grub_alias_devpath = grub_ieee1275_encode_devname (alias.path);
+ }
+ grub_free (canon);
+ }
+}
+
+static void
+display_disks (void)
+{
+ struct disk_dev *dev;
+
+ grub_printf ("--------------------- DISKS ---------------------\n");
+
+ FOR_LIST_ELEMENTS (dev, disk_devs)
+ {
+ grub_printf ("name: %s\n", dev->name);
+ grub_printf ("grub_devpath: %s\n", dev->grub_devpath);
+ grub_printf ("grub_alias_devpath: %s\n", dev->grub_alias_devpath);
+ grub_printf ("valid: %s\n", (dev->valid) ? "yes" : "no");
+ grub_printf ("boot_dev: %s\n", (dev->boot_dev) ? "yes" : "no");
+ grub_printf ("opened: %s\n", (dev->ihandle) ? "yes" : "no");
+ grub_printf ("block size: %" PRIuGRUB_UINT32_T "\n",
+ dev->block_size);
+ grub_printf ("num blocks: %" PRIuGRUB_UINT64_T "\n",
+ dev->num_blocks);
+ grub_printf ("log sector size: %" PRIuGRUB_UINT32_T "\n",
+ dev->log_sector_size);
+ grub_printf ("\n");
+ }
+
+ grub_printf ("-------------------------------------------------\n");
+}
+
+static void
+display_stats (void)
+{
+ const char *debug = grub_env_get ("debug");
+
+ if (debug == NULL)
+ return;
+
+ if (grub_strword (debug, "all") || grub_strword (debug, "obdisk"))
+ {
+ display_parents ();
+ display_disks ();
+ }
+}
+
+static void
+invalidate_all_disks (void)
+{
+ struct disk_dev *dev = NULL;
+
+ if (disks_enumerated != 0)
+ FOR_LIST_ELEMENTS (dev, disk_devs)
+ dev->valid = 0;
+}
+
+static struct disk_dev *
+find_legacy_grub_devpath (const char *name)
+{
+ struct disk_dev *dev = NULL;
+ char *canon, *devpath = NULL;
+
+ devpath = decode_grub_devname (name + sizeof ("ieee1275"));
+ canon = canonicalise_disk (devpath);
+
+ if (canon != NULL)
+ dev = grub_named_list_find (GRUB_AS_NAMED_LIST (disk_devs), canon);
+
+ grub_free (devpath);
+ grub_free (canon);
+ return dev;
+}
+
+static void
+enumerate_devices (void)
+{
+ invalidate_all_disks ();
+ enumerate_disks ();
+ enumerate_aliases ();
+ disks_enumerated = 1;
+ display_stats ();
+}
+
+static struct disk_dev *
+find_grub_devpath_real (const char *name)
+{
+ struct disk_dev *dev = NULL;
+
+ FOR_LIST_ELEMENTS (dev, disk_devs)
+ {
+ if ((STRCMP (dev->grub_devpath, name))
+ || (STRCMP (dev->grub_alias_devpath, name)))
+ break;
+ }
+
+ return dev;
+}
+
+static struct disk_dev *
+find_grub_devpath (const char *name)
+{
+ struct disk_dev *dev = NULL;
+ int enumerated;
+
+ do {
+ enumerated = disks_enumerated;
+ dev = find_grub_devpath_real (name);
+
+ if (dev != NULL)
+ break;
+
+ dev = find_legacy_grub_devpath (name);
+
+ if (dev != NULL)
+ break;
+
+ enumerate_devices ();
+ } while (enumerated == 0);
+
+ return dev;
+}
+
+static int
+grub_obdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct disk_dev *dev;
+ const char *name;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ enumerate_devices ();
+
+ FOR_LIST_ELEMENTS (dev, disk_devs)
+ {
+ if (dev->valid == 1)
+ {
+ if (dev->grub_alias_devpath)
+ name = dev->grub_alias_devpath;
+ else
+ name = dev->grub_devpath;
+
+ if (hook (name, hook_data))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_obdisk_open (const char *name, grub_disk_t disk)
+{
+ grub_ieee1275_ihandle_t ihandle = 0;
+ struct disk_dev *dev = NULL;
+
+ if (grub_strncmp (name, IEEE1275_DEV, sizeof (IEEE1275_DEV) - 1) != 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not IEEE1275 device");
+
+ dev = find_grub_devpath (name);
+
+ if (dev == NULL)
+ {
+ grub_printf ("UNKNOWN DEVICE: %s\n", name);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "%s", name);
+ }
+
+ if (dev->opened == 0)
+ {
+ if (dev->raw_name != NULL)
+ grub_ieee1275_open (dev->raw_name, &ihandle);
+ else
+ grub_ieee1275_open (dev->name, &ihandle);
+
+ if (ihandle == 0)
+ {
+ grub_printf ("Can't open device %s\n", name);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device %s", name);
+ }
+
+ dev->block_size = grub_ieee1275_get_block_size (ihandle);
+ dev->num_blocks = grub_ieee1275_num_blocks (ihandle);
+
+ if (dev->num_blocks == 0)
+ dev->num_blocks = grub_ieee1275_num_blocks64 (ihandle);
+
+ if (dev->num_blocks == 0)
+ dev->num_blocks = GRUB_DISK_SIZE_UNKNOWN;
+
+ if (dev->block_size != 0)
+ {
+ for (dev->log_sector_size = 0;
+ (1U << dev->log_sector_size) < dev->block_size;
+ dev->log_sector_size++);
+ }
+ else
+ dev->log_sector_size = 9;
+
+ dev->ihandle = ihandle;
+ dev->opened = 1;
+ }
+
+ disk->total_sectors = dev->num_blocks;
+ disk->id = dev->ihandle;
+ disk->data = dev;
+ disk->log_sector_size = dev->log_sector_size;
+ return GRUB_ERR_NONE;
+}
+
+
+static struct grub_disk_dev grub_obdisk_dev =
+ {
+ .name = "obdisk",
+ .id = GRUB_DISK_DEVICE_OBDISK_ID,
+ .disk_iterate = grub_obdisk_iterate,
+ .disk_open = grub_obdisk_open,
+ .disk_close = grub_obdisk_close,
+ .disk_read = grub_obdisk_read,
+ };
+
+void
+grub_obdisk_init (void)
+{
+ grub_disk_firmware_fini = grub_obdisk_fini;
+ add_bootpath ();
+ grub_disk_dev_register (&grub_obdisk_dev);
+}
+
+void
+grub_obdisk_fini (void)
+{
+ struct disk_dev *dev;
+
+ FOR_LIST_ELEMENTS (dev, disk_devs)
+ {
+ if (dev->opened != 0)
+ grub_ieee1275_close (dev->ihandle);
+ }
+
+ grub_disk_dev_unregister (&grub_obdisk_dev);
+}
diff --git a/grub-core/disk/ieee1275/ofdisk.c b/grub-core/disk/ieee1275/ofdisk.c
new file mode 100644
index 0000000..03674cb
--- /dev/null
+++ b/grub-core/disk/ieee1275/ofdisk.c
@@ -0,0 +1,741 @@
+/* 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>
+#include <grub/i18n.h>
+#include <grub/time.h>
+
+static char *last_devpath;
+static grub_ieee1275_ihandle_t last_ihandle;
+
+struct ofdisk_hash_ent
+{
+ char *devpath;
+ char *open_path;
+ char *grub_devpath;
+ int is_boot;
+ int is_removable;
+ int block_size_fails;
+ /* Pointer to shortest available name on nodes representing canonical names,
+ otherwise NULL. */
+ const char *shortest;
+ const char *grub_shortest;
+ struct ofdisk_hash_ent *next;
+};
+
+static grub_err_t
+grub_ofdisk_get_block_size (const char *device, grub_uint32_t *block_size,
+ struct ofdisk_hash_ent *op);
+
+#define OFDISK_HASH_SZ 8
+static struct ofdisk_hash_ent *ofdisk_hash[OFDISK_HASH_SZ];
+
+static int
+ofdisk_hash_fn (const char *devpath)
+{
+ int hash = 0;
+ while (*devpath)
+ hash ^= *devpath++;
+ return (hash & (OFDISK_HASH_SZ - 1));
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_find (const char *devpath)
+{
+ struct ofdisk_hash_ent *p = ofdisk_hash[ofdisk_hash_fn(devpath)];
+
+ while (p)
+ {
+ if (!grub_strcmp (p->devpath, devpath))
+ break;
+ p = p->next;
+ }
+ return p;
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_add_real (char *devpath)
+{
+ struct ofdisk_hash_ent *p;
+ struct ofdisk_hash_ent **head = &ofdisk_hash[ofdisk_hash_fn(devpath)];
+ const char *iptr;
+ char *optr;
+
+ p = grub_zalloc (sizeof (*p));
+ if (!p)
+ return NULL;
+
+ p->devpath = devpath;
+
+ p->grub_devpath = grub_malloc (sizeof ("ieee1275/")
+ + 2 * grub_strlen (p->devpath));
+
+ if (!p->grub_devpath)
+ {
+ grub_free (p);
+ return NULL;
+ }
+
+ if (! grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PARTITION_0))
+ {
+ p->open_path = grub_malloc (grub_strlen (p->devpath) + 3);
+ if (!p->open_path)
+ {
+ grub_free (p->grub_devpath);
+ grub_free (p);
+ return NULL;
+ }
+ optr = grub_stpcpy (p->open_path, p->devpath);
+ *optr++ = ':';
+ *optr++ = '0';
+ *optr = '\0';
+ }
+ else
+ p->open_path = p->devpath;
+
+ optr = grub_stpcpy (p->grub_devpath, "ieee1275/");
+ for (iptr = p->devpath; *iptr; )
+ {
+ if (*iptr == ',')
+ *optr++ = '\\';
+ *optr++ = *iptr++;
+ }
+ *optr = 0;
+
+ p->next = *head;
+ *head = p;
+ return p;
+}
+
+static int
+check_string_removable (const char *str)
+{
+ const char *ptr = grub_strrchr (str, '/');
+
+ if (ptr)
+ ptr++;
+ else
+ ptr = str;
+ return (grub_strncmp (ptr, "cdrom", 5) == 0 || grub_strncmp (ptr, "fd", 2) == 0);
+}
+
+static struct ofdisk_hash_ent *
+ofdisk_hash_add (char *devpath, char *curcan)
+{
+ struct ofdisk_hash_ent *p, *pcan;
+
+ p = ofdisk_hash_add_real (devpath);
+
+ grub_dprintf ("disk", "devpath = %s, canonical = %s\n", devpath, curcan);
+
+ if (!curcan)
+ {
+ p->shortest = p->devpath;
+ p->grub_shortest = p->grub_devpath;
+ if (check_string_removable (devpath))
+ p->is_removable = 1;
+ return p;
+ }
+
+ pcan = ofdisk_hash_find (curcan);
+ if (!pcan)
+ pcan = ofdisk_hash_add_real (curcan);
+ else
+ grub_free (curcan);
+
+ if (check_string_removable (devpath) || check_string_removable (curcan))
+ pcan->is_removable = 1;
+
+ if (!pcan)
+ grub_errno = GRUB_ERR_NONE;
+ else
+ {
+ if (!pcan->shortest
+ || grub_strlen (pcan->shortest) > grub_strlen (devpath))
+ {
+ pcan->shortest = p->devpath;
+ pcan->grub_shortest = p->grub_devpath;
+ }
+ }
+
+ return p;
+}
+
+static void
+dev_iterate_real (const char *name, const char *path)
+{
+ struct ofdisk_hash_ent *op;
+
+ grub_dprintf ("disk", "disk name = %s, path = %s\n", name,
+ path);
+
+ op = ofdisk_hash_find (path);
+ if (!op)
+ {
+ char *name_dup = grub_strdup (name);
+ char *can = grub_strdup (path);
+ if (!name_dup || !can)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ grub_free (name_dup);
+ grub_free (can);
+ return;
+ }
+ op = ofdisk_hash_add (name_dup, can);
+ }
+ return;
+}
+
+static void
+dev_iterate (const struct grub_ieee1275_devalias *alias)
+{
+ if (grub_strcmp (alias->type, "vscsi") == 0)
+ {
+ static grub_ieee1275_ihandle_t ihandle;
+ struct set_color_args
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t ihandle;
+ grub_ieee1275_cell_t catch_result;
+ grub_ieee1275_cell_t nentries;
+ grub_ieee1275_cell_t table;
+ }
+ args;
+ char *buf, *bufptr;
+ unsigned i;
+
+ if (grub_ieee1275_open (alias->path, &ihandle))
+ return;
+
+ /* This method doesn't need memory allocation for the table. Open
+ firmware takes care of all memory management and the result table
+ stays in memory and is never freed. */
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 2, 3);
+ args.method = (grub_ieee1275_cell_t) "vscsi-report-luns";
+ args.ihandle = ihandle;
+ args.table = 0;
+ args.nentries = 0;
+
+ if (IEEE1275_CALL_ENTRY_FN (&args) == -1 || args.catch_result)
+ {
+ grub_ieee1275_close (ihandle);
+ return;
+ }
+
+ buf = grub_malloc (grub_strlen (alias->path) + 32);
+ if (!buf)
+ return;
+ bufptr = grub_stpcpy (buf, alias->path);
+
+ for (i = 0; i < args.nentries; i++)
+ {
+ grub_uint64_t *ptr;
+
+ ptr = *(grub_uint64_t **) (args.table + 4 + 8 * i);
+ while (*ptr)
+ {
+ grub_snprintf (bufptr, 32, "/disk@%" PRIxGRUB_UINT64_T, *ptr++);
+ dev_iterate_real (buf, buf);
+ }
+ }
+ grub_ieee1275_close (ihandle);
+ grub_free (buf);
+ return;
+ }
+ else if (grub_strcmp (alias->type, "sas_ioa") == 0)
+ {
+ /* The method returns the number of disks and a table where
+ * each ID is 64-bit long. Example of sas paths:
+ * /pci@80000002000001f/pci1014,034A@0/sas/disk@c05db70800
+ * /pci@80000002000001f/pci1014,034A@0/sas/disk@a05db70800
+ * /pci@80000002000001f/pci1014,034A@0/sas/disk@805db70800 */
+
+ struct sas_children
+ {
+ struct grub_ieee1275_common_hdr common;
+ grub_ieee1275_cell_t method;
+ grub_ieee1275_cell_t ihandle;
+ grub_ieee1275_cell_t max;
+ grub_ieee1275_cell_t table;
+ grub_ieee1275_cell_t catch_result;
+ grub_ieee1275_cell_t nentries;
+ }
+ args;
+ char *buf, *bufptr;
+ unsigned i;
+ grub_uint64_t *table;
+ grub_uint16_t table_size;
+ grub_ieee1275_ihandle_t ihandle;
+
+ buf = grub_malloc (grub_strlen (alias->path) +
+ sizeof ("/disk@7766554433221100"));
+ if (!buf)
+ return;
+ bufptr = grub_stpcpy (buf, alias->path);
+
+ /* Power machines documentation specify 672 as maximum SAS disks in
+ one system. Using a slightly larger value to be safe. */
+ table_size = 768;
+ table = grub_calloc (table_size, sizeof (grub_uint64_t));
+
+ if (!table)
+ {
+ grub_free (buf);
+ return;
+ }
+
+ if (grub_ieee1275_open (alias->path, &ihandle))
+ {
+ grub_free (buf);
+ grub_free (table);
+ return;
+ }
+
+ INIT_IEEE1275_COMMON (&args.common, "call-method", 4, 2);
+ args.method = (grub_ieee1275_cell_t) "get-sas-children";
+ args.ihandle = ihandle;
+ args.max = table_size;
+ args.table = (grub_ieee1275_cell_t) table;
+ args.catch_result = 0;
+ args.nentries = 0;
+
+ if (IEEE1275_CALL_ENTRY_FN (&args) == -1)
+ {
+ grub_ieee1275_close (ihandle);
+ grub_free (table);
+ grub_free (buf);
+ return;
+ }
+
+ for (i = 0; i < args.nentries; i++)
+ {
+ grub_snprintf (bufptr, sizeof ("/disk@7766554433221100"),
+ "/disk@%" PRIxGRUB_UINT64_T, table[i]);
+ dev_iterate_real (buf, buf);
+ }
+
+ grub_ieee1275_close (ihandle);
+ grub_free (table);
+ grub_free (buf);
+ }
+
+ if (!grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_TREE_SCANNING_FOR_DISKS)
+ && grub_strcmp (alias->type, "block") == 0)
+ {
+ dev_iterate_real (alias->path, alias->path);
+ return;
+ }
+
+ {
+ struct grub_ieee1275_devalias child;
+
+ FOR_IEEE1275_DEVCHILDREN(alias->path, child)
+ dev_iterate (&child);
+ }
+}
+
+static void
+scan (void)
+{
+ struct grub_ieee1275_devalias alias;
+ FOR_IEEE1275_DEVALIASES(alias)
+ {
+ if (grub_strcmp (alias.type, "block") != 0)
+ continue;
+ dev_iterate_real (alias.name, alias.path);
+ }
+
+ FOR_IEEE1275_DEVCHILDREN("/", alias)
+ dev_iterate (&alias);
+}
+
+static int
+grub_ofdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ unsigned i;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ scan ();
+
+ for (i = 0; i < ARRAY_SIZE (ofdisk_hash); i++)
+ {
+ static struct ofdisk_hash_ent *ent;
+ for (ent = ofdisk_hash[i]; ent; ent = ent->next)
+ {
+ if (!ent->shortest)
+ continue;
+ if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_OFDISK_SDCARD_ONLY))
+ {
+ grub_ieee1275_phandle_t dev;
+ char tmp[8];
+
+ if (grub_ieee1275_finddevice (ent->devpath, &dev))
+ {
+ grub_dprintf ("disk", "finddevice (%s) failed\n",
+ ent->devpath);
+ continue;
+ }
+
+ if (grub_ieee1275_get_property (dev, "iconname", tmp,
+ sizeof tmp, 0))
+ {
+ grub_dprintf ("disk", "get iconname failed\n");
+ continue;
+ }
+
+ if (grub_strcmp (tmp, "sdmmc") != 0)
+ {
+ grub_dprintf ("disk", "device is not an SD card\n");
+ continue;
+ }
+ }
+
+ if (!ent->is_boot && ent->is_removable)
+ continue;
+
+ if (hook (ent->grub_shortest, hook_data))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static char *
+compute_dev_path (const char *name)
+{
+ char *devpath = grub_malloc (grub_strlen (name) + 3);
+ char *p, c;
+
+ if (!devpath)
+ return NULL;
+
+ /* Un-escape commas. */
+ p = devpath;
+ while ((c = *name++) != '\0')
+ {
+ if (c == '\\' && *name == ',')
+ {
+ *p++ = ',';
+ name++;
+ }
+ else
+ *p++ = c;
+ }
+
+ *p++ = '\0';
+
+ return devpath;
+}
+
+static grub_err_t
+grub_ofdisk_open (const char *name, grub_disk_t disk)
+{
+ grub_ieee1275_phandle_t dev;
+ char *devpath;
+ /* XXX: This should be large enough for any possible case. */
+ char prop[64];
+ grub_ssize_t actual;
+ grub_uint32_t block_size = 0;
+ grub_err_t err;
+
+ if (grub_strncmp (name, "ieee1275/", sizeof ("ieee1275/") - 1) != 0)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "not IEEE1275 device");
+ devpath = compute_dev_path (name + sizeof ("ieee1275/") - 1);
+ if (! devpath)
+ return grub_errno;
+
+ grub_dprintf ("disk", "Opening `%s'.\n", devpath);
+
+ if (grub_ieee1275_finddevice (devpath, &dev))
+ {
+ grub_free (devpath);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "can't read device properties");
+ }
+
+ if (grub_ieee1275_get_property (dev, "device_type", prop, sizeof (prop),
+ &actual))
+ {
+ grub_free (devpath);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't read the device type");
+ }
+
+ if (grub_strcmp (prop, "block"))
+ {
+ grub_free (devpath);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a block device");
+ }
+
+ /* XXX: There is no property to read the number of blocks. There
+ should be a property `#blocks', but it is not there. Perhaps it
+ is possible to use seek for this. */
+ disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+
+ {
+ struct ofdisk_hash_ent *op;
+ op = ofdisk_hash_find (devpath);
+ if (!op)
+ op = ofdisk_hash_add (devpath, NULL);
+ if (!op)
+ {
+ grub_free (devpath);
+ return grub_errno;
+ }
+ disk->id = (unsigned long) op;
+ disk->data = op->open_path;
+
+ err = grub_ofdisk_get_block_size (devpath, &block_size, op);
+ if (err)
+ {
+ grub_free (devpath);
+ return err;
+ }
+ if (block_size != 0)
+ {
+ for (disk->log_sector_size = 0;
+ (1U << disk->log_sector_size) < block_size;
+ disk->log_sector_size++);
+ }
+ else
+ disk->log_sector_size = 9;
+ }
+
+ grub_free (devpath);
+ return 0;
+}
+
+static void
+grub_ofdisk_close (grub_disk_t disk)
+{
+ if (disk->data == last_devpath)
+ {
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+ }
+ disk->data = 0;
+}
+
+static grub_err_t
+grub_ofdisk_prepare (grub_disk_t disk, grub_disk_addr_t sector)
+{
+ grub_ssize_t status;
+ unsigned long long pos;
+
+ if (disk->data != last_devpath)
+ {
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+
+ grub_ieee1275_open (disk->data, &last_ihandle);
+ if (! last_ihandle)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+ last_devpath = disk->data;
+ }
+
+ pos = sector << disk->log_sector_size;
+
+ grub_ieee1275_seek (last_ihandle, pos, &status);
+ if (status < 0)
+ return grub_error (GRUB_ERR_READ_ERROR,
+ "seek error, can't seek block %llu",
+ (long long) sector);
+ return 0;
+}
+
+static grub_err_t
+grub_ofdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_err_t err;
+ grub_ssize_t actual;
+ err = grub_ofdisk_prepare (disk, sector);
+ if (err)
+ return err;
+ grub_ieee1275_read (last_ihandle, buf, size << disk->log_sector_size,
+ &actual);
+ if (actual != (grub_ssize_t) (size << disk->log_sector_size))
+ return grub_error (GRUB_ERR_READ_ERROR, N_("failure reading sector 0x%llx "
+ "from `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+
+ return 0;
+}
+
+static grub_err_t
+grub_ofdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ grub_err_t err;
+ grub_ssize_t actual;
+ err = grub_ofdisk_prepare (disk, sector);
+ if (err)
+ return err;
+ grub_ieee1275_write (last_ihandle, buf, size << disk->log_sector_size,
+ &actual);
+ if (actual != (grub_ssize_t) (size << disk->log_sector_size))
+ return grub_error (GRUB_ERR_WRITE_ERROR, N_("failure writing sector 0x%llx "
+ "to `%s'"),
+ (unsigned long long) sector,
+ disk->name);
+
+ return 0;
+}
+
+static struct grub_disk_dev grub_ofdisk_dev =
+ {
+ .name = "ofdisk",
+ .id = GRUB_DISK_DEVICE_OFDISK_ID,
+ .disk_iterate = grub_ofdisk_iterate,
+ .disk_open = grub_ofdisk_open,
+ .disk_close = grub_ofdisk_close,
+ .disk_read = grub_ofdisk_read,
+ .disk_write = grub_ofdisk_write,
+ .next = 0
+ };
+
+static void
+insert_bootpath (void)
+{
+ char *bootpath;
+ grub_ssize_t bootpath_size;
+ char *type;
+
+ if (grub_ieee1275_get_property_length (grub_ieee1275_chosen, "bootpath",
+ &bootpath_size)
+ || bootpath_size <= 0)
+ {
+ /* Should never happen. */
+ grub_printf ("/chosen/bootpath property missing!\n");
+ return;
+ }
+
+ bootpath = (char *) grub_malloc ((grub_size_t) bootpath_size + 64);
+ if (! bootpath)
+ {
+ grub_print_error ();
+ return;
+ }
+ grub_ieee1275_get_property (grub_ieee1275_chosen, "bootpath", bootpath,
+ (grub_size_t) bootpath_size + 1, 0);
+ bootpath[bootpath_size] = '\0';
+
+ /* Transform an OF device path to a GRUB path. */
+
+ type = grub_ieee1275_get_device_type (bootpath);
+ if (!(type && grub_strcmp (type, "network") == 0))
+ {
+ struct ofdisk_hash_ent *op;
+ char *device = grub_ieee1275_get_devname (bootpath);
+ op = ofdisk_hash_add (device, NULL);
+ op->is_boot = 1;
+ }
+ grub_free (type);
+ grub_free (bootpath);
+}
+
+void
+grub_ofdisk_fini (void)
+{
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+ last_ihandle = 0;
+ last_devpath = NULL;
+
+ grub_disk_dev_unregister (&grub_ofdisk_dev);
+}
+
+void
+grub_ofdisk_init (void)
+{
+ grub_disk_firmware_fini = grub_ofdisk_fini;
+
+ insert_bootpath ();
+
+ grub_disk_dev_register (&grub_ofdisk_dev);
+}
+
+static grub_err_t
+grub_ofdisk_get_block_size (const char *device, grub_uint32_t *block_size,
+ struct ofdisk_hash_ent *op)
+{
+ struct size_args_ieee1275
+ {
+ 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_ieee1275;
+
+ if (last_ihandle)
+ grub_ieee1275_close (last_ihandle);
+
+ last_ihandle = 0;
+ last_devpath = NULL;
+
+ grub_ieee1275_open (device, &last_ihandle);
+ if (! last_ihandle)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+
+ *block_size = 0;
+
+ if (op->block_size_fails >= 2)
+ return GRUB_ERR_NONE;
+
+ INIT_IEEE1275_COMMON (&args_ieee1275.common, "call-method", 2, 2);
+ args_ieee1275.method = (grub_ieee1275_cell_t) "block-size";
+ args_ieee1275.ihandle = last_ihandle;
+ args_ieee1275.result = 1;
+
+ if (IEEE1275_CALL_ENTRY_FN (&args_ieee1275) == -1)
+ {
+ grub_dprintf ("disk", "can't get block size: failed call-method\n");
+ op->block_size_fails++;
+ }
+ else if (args_ieee1275.result)
+ {
+ grub_dprintf ("disk", "can't get block size: %lld\n",
+ (long long) args_ieee1275.result);
+ op->block_size_fails++;
+ }
+ else if (args_ieee1275.size1
+ && !(args_ieee1275.size1 & (args_ieee1275.size1 - 1))
+ && args_ieee1275.size1 >= 512 && args_ieee1275.size1 <= 16384)
+ {
+ op->block_size_fails = 0;
+ *block_size = args_ieee1275.size1;
+ }
+
+ return 0;
+}
diff --git a/grub-core/disk/ldm.c b/grub-core/disk/ldm.c
new file mode 100644
index 0000000..4577a51
--- /dev/null
+++ b/grub-core/disk/ldm.c
@@ -0,0 +1,1110 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008,2009,2011 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/diskfilter.h>
+#include <grub/msdos_partition.h>
+#include <grub/gpt_partition.h>
+#include <grub/i18n.h>
+#include <grub/safemath.h>
+
+#ifdef GRUB_UTIL
+#include <grub/emu/misc.h>
+#include <grub/emu/hostdisk.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define LDM_GUID_STRLEN 64
+#define LDM_NAME_STRLEN 32
+
+typedef grub_uint8_t *grub_ldm_id_t;
+
+enum { STRIPE = 1, SPANNED = 2, RAID5 = 3 };
+
+#define LDM_LABEL_SECTOR 6
+struct grub_ldm_vblk {
+ char magic[4];
+ grub_uint8_t unused1[12];
+ grub_uint16_t update_status;
+ grub_uint8_t flags;
+ grub_uint8_t type;
+ grub_uint32_t unused2;
+ grub_uint8_t dynamic[104];
+} GRUB_PACKED;
+#define LDM_VBLK_MAGIC "VBLK"
+
+enum
+ {
+ STATUS_CONSISTENT = 0,
+ STATUS_STILL_ACTIVE = 1,
+ STATUS_NOT_ACTIVE_YET = 2
+ };
+
+enum
+ {
+ ENTRY_COMPONENT = 0x32,
+ ENTRY_PARTITION = 0x33,
+ ENTRY_DISK = 0x34,
+ ENTRY_VOLUME = 0x51,
+ };
+
+struct grub_ldm_label
+{
+ char magic[8];
+ grub_uint32_t unused1;
+ grub_uint16_t ver_major;
+ grub_uint16_t ver_minor;
+ grub_uint8_t unused2[32];
+ char disk_guid[LDM_GUID_STRLEN];
+ char host_guid[LDM_GUID_STRLEN];
+ char group_guid[LDM_GUID_STRLEN];
+ char group_name[LDM_NAME_STRLEN];
+ grub_uint8_t unused3[11];
+ grub_uint64_t pv_start;
+ grub_uint64_t pv_size;
+ grub_uint64_t config_start;
+ grub_uint64_t config_size;
+} GRUB_PACKED;
+
+
+#define LDM_MAGIC "PRIVHEAD"
+
+
+
+static inline grub_uint64_t
+read_int (grub_uint8_t *in, grub_size_t s)
+{
+ grub_uint8_t *ptr2;
+ grub_uint64_t ret;
+ ret = 0;
+ for (ptr2 = in; ptr2 < in + s; ptr2++)
+ {
+ ret <<= 8;
+ ret |= *ptr2;
+ }
+ return ret;
+}
+
+static int
+check_ldm_partition (grub_disk_t disk __attribute__ ((unused)), const grub_partition_t p, void *data)
+{
+ int *has_ldm = data;
+
+ if (p->number >= 4)
+ return 1;
+ if (p->msdostype == GRUB_PC_PARTITION_TYPE_LDM)
+ {
+ *has_ldm = 1;
+ return 1;
+ }
+ return 0;
+}
+
+static int
+msdos_has_ldm_partition (grub_disk_t dsk)
+{
+ grub_err_t err;
+ int has_ldm = 0;
+
+ err = grub_partition_msdos_iterate (dsk, check_ldm_partition, &has_ldm);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+
+ return has_ldm;
+}
+
+static const grub_gpt_part_guid_t ldm_type = GRUB_GPT_PARTITION_TYPE_LDM;
+
+/* Helper for gpt_ldm_sector. */
+static int
+gpt_ldm_sector_iter (grub_disk_t disk, const grub_partition_t p, void *data)
+{
+ grub_disk_addr_t *sector = data;
+ struct grub_gpt_partentry gptdata;
+ grub_partition_t p2;
+
+ p2 = disk->partition;
+ disk->partition = p->parent;
+ if (grub_disk_read (disk, p->offset, p->index,
+ sizeof (gptdata), &gptdata))
+ {
+ disk->partition = p2;
+ return 0;
+ }
+ disk->partition = p2;
+
+ if (! grub_memcmp (&gptdata.type, &ldm_type, 16))
+ {
+ *sector = p->start + p->len - 1;
+ return 1;
+ }
+ return 0;
+}
+
+static grub_disk_addr_t
+gpt_ldm_sector (grub_disk_t dsk)
+{
+ grub_disk_addr_t sector = 0;
+ grub_err_t err;
+
+ err = grub_gpt_partition_map_iterate (dsk, gpt_ldm_sector_iter, &sector);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ return sector;
+}
+
+static struct grub_diskfilter_vg *
+make_vg (grub_disk_t disk,
+ const struct grub_ldm_label *label)
+{
+ grub_disk_addr_t startsec, endsec, cursec;
+ struct grub_diskfilter_vg *vg;
+ grub_err_t err;
+
+ /* First time we see this volume group. We've to create the
+ whole volume group structure. */
+ vg = grub_malloc (sizeof (*vg));
+ if (! vg)
+ return NULL;
+ vg->extent_size = 1;
+ vg->name = grub_malloc (LDM_NAME_STRLEN + 1);
+ vg->uuid = grub_malloc (LDM_GUID_STRLEN + 1);
+ if (! vg->uuid || !vg->name)
+ {
+ grub_free (vg->uuid);
+ grub_free (vg->name);
+ grub_free (vg);
+ return NULL;
+ }
+ grub_memcpy (vg->uuid, label->group_guid, LDM_GUID_STRLEN);
+ grub_memcpy (vg->name, label->group_name, LDM_NAME_STRLEN);
+ vg->name[LDM_NAME_STRLEN] = 0;
+ vg->uuid[LDM_GUID_STRLEN] = 0;
+ vg->uuid_len = grub_strlen (vg->uuid);
+
+ vg->lvs = NULL;
+ vg->pvs = NULL;
+
+ startsec = grub_be_to_cpu64 (label->config_start);
+ endsec = startsec + grub_be_to_cpu64 (label->config_size);
+
+ /* First find disks. */
+ for (cursec = startsec + 0x12; cursec < endsec; cursec++)
+ {
+ struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE
+ / sizeof (struct grub_ldm_vblk)];
+ unsigned i;
+ err = grub_disk_read (disk, cursec, 0,
+ sizeof(vblk), &vblk);
+ if (err)
+ goto fail2;
+
+ for (i = 0; i < ARRAY_SIZE (vblk); i++)
+ {
+ struct grub_diskfilter_pv *pv;
+ grub_uint8_t *ptr;
+ if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC,
+ sizeof (vblk[i].magic)) != 0)
+ continue;
+ if (grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_CONSISTENT
+ && grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_STILL_ACTIVE)
+ continue;
+ if (vblk[i].type != ENTRY_DISK)
+ continue;
+ pv = grub_zalloc (sizeof (*pv));
+ if (!pv)
+ goto fail2;
+
+ pv->disk = 0;
+ ptr = vblk[i].dynamic;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (pv);
+ goto fail2;
+ }
+ pv->internal_id = grub_malloc (ptr[0] + 2);
+ if (!pv->internal_id)
+ {
+ grub_free (pv);
+ goto fail2;
+ }
+ grub_memcpy (pv->internal_id, ptr, (grub_size_t) ptr[0] + 1);
+ pv->internal_id[(grub_size_t) ptr[0] + 1] = 0;
+
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (pv);
+ goto fail2;
+ }
+ /* ptr = name. */
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1
+ >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (pv);
+ goto fail2;
+ }
+ pv->id.uuidlen = *ptr;
+ pv->id.uuid = grub_malloc (pv->id.uuidlen + 1);
+ grub_memcpy (pv->id.uuid, ptr + 1, pv->id.uuidlen);
+ pv->id.uuid[pv->id.uuidlen] = 0;
+
+ pv->next = vg->pvs;
+ vg->pvs = pv;
+ }
+ }
+
+ /* Then find LVs. */
+ for (cursec = startsec + 0x12; cursec < endsec; cursec++)
+ {
+ struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE
+ / sizeof (struct grub_ldm_vblk)];
+ unsigned i;
+ grub_size_t sz;
+ err = grub_disk_read (disk, cursec, 0,
+ sizeof(vblk), &vblk);
+ if (err)
+ goto fail2;
+
+ for (i = 0; i < ARRAY_SIZE (vblk); i++)
+ {
+ struct grub_diskfilter_lv *lv;
+ grub_uint8_t *ptr;
+ if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC,
+ sizeof (vblk[i].magic)) != 0)
+ continue;
+ if (grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_CONSISTENT
+ && grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_STILL_ACTIVE)
+ continue;
+ if (vblk[i].type != ENTRY_VOLUME)
+ continue;
+ lv = grub_zalloc (sizeof (*lv));
+ if (!lv)
+ goto fail2;
+
+ lv->vg = vg;
+ lv->segment_count = 1;
+ lv->segment_alloc = 1;
+ lv->visible = 1;
+ lv->segments = grub_zalloc (sizeof (*lv->segments));
+ if (!lv->segments)
+ {
+ grub_free (lv);
+ goto fail2;
+ }
+ lv->segments->start_extent = 0;
+ lv->segments->type = GRUB_DISKFILTER_MIRROR;
+ lv->segments->node_count = 0;
+ lv->segments->node_alloc = 8;
+ lv->segments->nodes = grub_calloc (lv->segments->node_alloc,
+ sizeof (*lv->segments->nodes));
+ if (!lv->segments->nodes)
+ {
+ grub_free (lv);
+ goto fail2;
+ }
+ ptr = vblk[i].dynamic;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv);
+ goto fail2;
+ }
+ lv->internal_id = grub_malloc ((grub_size_t) ptr[0] + 2);
+ if (!lv->internal_id)
+ {
+ grub_free (lv);
+ goto fail2;
+ }
+ grub_memcpy (lv->internal_id, ptr, ptr[0] + 1);
+ lv->internal_id[ptr[0] + 1] = 0;
+
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv);
+ goto fail2;
+ }
+ if (grub_add (*ptr, 1, &sz))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv);
+ goto fail2;
+ }
+ lv->name = grub_malloc (sz);
+ if (!lv->name)
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv);
+ goto fail2;
+ }
+ grub_memcpy (lv->name, ptr + 1, *ptr);
+ lv->name[*ptr] = 0;
+ lv->fullname = grub_xasprintf ("ldm/%s/%s",
+ vg->uuid, lv->name);
+ if (!lv->fullname)
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1
+ >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+ /* ptr = volume type. */
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+ /* ptr = flags. */
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+
+ /* Skip state, type, unknown, volume number, zeros, flags. */
+ ptr += 14 + 1 + 1 + 1 + 3 + 1;
+ /* ptr = number of children. */
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+
+ /* Skip 2 more fields. */
+ ptr += 8 + 8;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1>= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail2;
+ }
+ lv->size = read_int (ptr + 1, *ptr);
+ lv->segments->extent_count = lv->size;
+
+ lv->next = vg->lvs;
+ vg->lvs = lv;
+ }
+ }
+
+ /* Now the components. */
+ for (cursec = startsec + 0x12; cursec < endsec; cursec++)
+ {
+ struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE
+ / sizeof (struct grub_ldm_vblk)];
+ unsigned i;
+ err = grub_disk_read (disk, cursec, 0,
+ sizeof(vblk), &vblk);
+ if (err)
+ goto fail2;
+
+ for (i = 0; i < ARRAY_SIZE (vblk); i++)
+ {
+ struct grub_diskfilter_lv *comp;
+ struct grub_diskfilter_lv *lv;
+ grub_uint8_t type;
+
+ grub_uint8_t *ptr;
+ if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC,
+ sizeof (vblk[i].magic)) != 0)
+ continue;
+ if (grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_CONSISTENT
+ && grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_STILL_ACTIVE)
+ continue;
+ if (vblk[i].type != ENTRY_COMPONENT)
+ continue;
+ comp = grub_zalloc (sizeof (*comp));
+ if (!comp)
+ goto fail2;
+ comp->visible = 0;
+ comp->name = 0;
+ comp->fullname = 0;
+
+ ptr = vblk[i].dynamic;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ comp->internal_id = grub_malloc ((grub_size_t) ptr[0] + 2);
+ if (!comp->internal_id)
+ {
+ grub_free (comp);
+ goto fail2;
+ }
+ grub_memcpy (comp->internal_id, ptr, ptr[0] + 1);
+ comp->internal_id[ptr[0] + 1] = 0;
+
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ /* ptr = name. */
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ /* ptr = state. */
+ ptr += *ptr + 1;
+ type = *ptr++;
+ /* skip zeros. */
+ ptr += 4;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+
+ /* ptr = number of children. */
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ ptr += 8 + 8;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ for (lv = vg->lvs; lv; lv = lv->next)
+ {
+ if (lv->internal_id[0] == ptr[0]
+ && grub_memcmp (lv->internal_id + 1, ptr + 1, ptr[0]) == 0)
+ break;
+ }
+ if (!lv)
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ continue;
+ }
+ comp->size = lv->size;
+ if (type == SPANNED)
+ {
+ comp->segment_alloc = 8;
+ comp->segment_count = 0;
+ comp->segments = grub_calloc (comp->segment_alloc,
+ sizeof (*comp->segments));
+ if (!comp->segments)
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ }
+ else
+ {
+ comp->segment_alloc = 1;
+ comp->segment_count = 1;
+ comp->segments = grub_malloc (sizeof (*comp->segments));
+ if (!comp->segments)
+ {
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ comp->segments->start_extent = 0;
+ comp->segments->extent_count = lv->size;
+ comp->segments->layout = 0;
+ if (type == STRIPE)
+ comp->segments->type = GRUB_DISKFILTER_STRIPED;
+ else if (type == RAID5)
+ {
+ comp->segments->type = GRUB_DISKFILTER_RAID5;
+ comp->segments->layout = GRUB_RAID_LAYOUT_SYMMETRIC_MASK;
+ }
+ else
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ ptr += *ptr + 1;
+ ptr++;
+ if (!(vblk[i].flags & 0x10))
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ comp->segments->stripe_size = read_int (ptr + 1, *ptr);
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ comp->segments->node_count = read_int (ptr + 1, *ptr);
+ comp->segments->node_alloc = comp->segments->node_count;
+ comp->segments->nodes = grub_calloc (comp->segments->node_alloc,
+ sizeof (*comp->segments->nodes));
+ if (!lv->segments->nodes)
+ {
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ }
+
+ if (lv->segments->node_alloc == lv->segments->node_count)
+ {
+ void *t;
+ grub_size_t sz;
+
+ if (grub_mul (lv->segments->node_alloc, 2, &lv->segments->node_alloc) ||
+ grub_mul (lv->segments->node_alloc, sizeof (*lv->segments->nodes), &sz))
+ {
+ grub_free (comp->segments->nodes);
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+
+ t = grub_realloc (lv->segments->nodes, sz);
+ if (!t)
+ {
+ grub_free (comp->segments->nodes);
+ grub_free (comp->segments);
+ grub_free (comp->internal_id);
+ grub_free (comp);
+ goto fail2;
+ }
+ lv->segments->nodes = t;
+ }
+ lv->segments->nodes[lv->segments->node_count].pv = 0;
+ lv->segments->nodes[lv->segments->node_count].start = 0;
+ lv->segments->nodes[lv->segments->node_count++].lv = comp;
+ comp->next = vg->lvs;
+ vg->lvs = comp;
+ }
+ }
+ /* Partitions. */
+ for (cursec = startsec + 0x12; cursec < endsec; cursec++)
+ {
+ struct grub_ldm_vblk vblk[GRUB_DISK_SECTOR_SIZE
+ / sizeof (struct grub_ldm_vblk)];
+ unsigned i;
+ err = grub_disk_read (disk, cursec, 0,
+ sizeof(vblk), &vblk);
+ if (err)
+ goto fail2;
+
+ for (i = 0; i < ARRAY_SIZE (vblk); i++)
+ {
+ struct grub_diskfilter_lv *comp;
+ struct grub_diskfilter_node part;
+ grub_disk_addr_t start, size;
+
+ grub_uint8_t *ptr;
+ part.name = 0;
+ if (grub_memcmp (vblk[i].magic, LDM_VBLK_MAGIC,
+ sizeof (vblk[i].magic)) != 0)
+ continue;
+ if (grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_CONSISTENT
+ && grub_be_to_cpu16 (vblk[i].update_status)
+ != STATUS_STILL_ACTIVE)
+ continue;
+ if (vblk[i].type != ENTRY_PARTITION)
+ continue;
+ part.lv = 0;
+ part.pv = 0;
+
+ ptr = vblk[i].dynamic;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ /* ID */
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ /* ptr = name. */
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+
+ /* skip zeros and logcommit id. */
+ ptr += 4 + 8;
+ if (ptr + 16 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ part.start = read_int (ptr, 8);
+ start = read_int (ptr + 8, 8);
+ ptr += 16;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ size = read_int (ptr + 1, *ptr);
+ ptr += *ptr + 1;
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+
+ for (comp = vg->lvs; comp; comp = comp->next)
+ if (comp->internal_id[0] == ptr[0]
+ && grub_memcmp (ptr + 1, comp->internal_id + 1,
+ comp->internal_id[0]) == 0)
+ goto out;
+ continue;
+ out:
+ if (ptr >= vblk[i].dynamic + sizeof (vblk[i].dynamic)
+ || ptr + *ptr + 1 >= vblk[i].dynamic + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ ptr += *ptr + 1;
+ struct grub_diskfilter_pv *pv;
+ for (pv = vg->pvs; pv; pv = pv->next)
+ if (pv->internal_id[0] == ptr[0]
+ && grub_memcmp (pv->internal_id + 1, ptr + 1, ptr[0]) == 0)
+ part.pv = pv;
+
+ if (comp->segment_alloc == 1)
+ {
+ unsigned node_index;
+ ptr += *ptr + 1;
+ if (ptr + *ptr + 1 >= vblk[i].dynamic
+ + sizeof (vblk[i].dynamic))
+ {
+ goto fail2;
+ }
+ node_index = read_int (ptr + 1, *ptr);
+ if (node_index < comp->segments->node_count)
+ comp->segments->nodes[node_index] = part;
+ }
+ else
+ {
+ if (comp->segment_alloc == comp->segment_count)
+ {
+ void *t;
+ grub_size_t sz;
+
+ if (grub_mul (comp->segment_alloc, 2, &comp->segment_alloc) ||
+ grub_mul (comp->segment_alloc, sizeof (*comp->segments), &sz))
+ goto fail2;
+
+ t = grub_realloc (comp->segments, sz);
+ if (!t)
+ goto fail2;
+ comp->segments = t;
+ }
+ comp->segments[comp->segment_count].start_extent = start;
+ comp->segments[comp->segment_count].extent_count = size;
+ comp->segments[comp->segment_count].type = GRUB_DISKFILTER_STRIPED;
+ comp->segments[comp->segment_count].node_count = 1;
+ comp->segments[comp->segment_count].node_alloc = 1;
+ comp->segments[comp->segment_count].nodes
+ = grub_malloc (sizeof (*comp->segments[comp->segment_count].nodes));
+ if (!comp->segments[comp->segment_count].nodes)
+ goto fail2;
+ comp->segments[comp->segment_count].nodes[0] = part;
+ comp->segment_count++;
+ }
+ }
+ }
+ if (grub_diskfilter_vg_register (vg))
+ goto fail2;
+ return vg;
+ fail2:
+ {
+ struct grub_diskfilter_lv *lv, *next_lv;
+ struct grub_diskfilter_pv *pv, *next_pv;
+ for (lv = vg->lvs; lv; lv = next_lv)
+ {
+ unsigned i;
+ for (i = 0; i < lv->segment_count; i++)
+ grub_free (lv->segments[i].nodes);
+
+ next_lv = lv->next;
+ grub_free (lv->segments);
+ grub_free (lv->internal_id);
+ grub_free (lv->name);
+ grub_free (lv->fullname);
+ grub_free (lv);
+ }
+ for (pv = vg->pvs; pv; pv = next_pv)
+ {
+ next_pv = pv->next;
+ grub_free (pv->id.uuid);
+ grub_free (pv);
+ }
+ }
+ grub_free (vg->uuid);
+ grub_free (vg);
+ return NULL;
+}
+
+static struct grub_diskfilter_vg *
+grub_ldm_detect (grub_disk_t disk,
+ struct grub_diskfilter_pv_id *id,
+ grub_disk_addr_t *start_sector)
+{
+ grub_err_t err;
+ struct grub_ldm_label label;
+ struct grub_diskfilter_vg *vg;
+
+#ifdef GRUB_UTIL
+ grub_util_info ("scanning %s for LDM", disk->name);
+#endif
+
+ {
+ int i;
+ int has_ldm = msdos_has_ldm_partition (disk);
+ for (i = 0; i < 3; i++)
+ {
+ grub_disk_addr_t sector = LDM_LABEL_SECTOR;
+ switch (i)
+ {
+ case 0:
+ if (!has_ldm)
+ continue;
+ sector = LDM_LABEL_SECTOR;
+ break;
+ case 1:
+ /* LDM is never inside a partition. */
+ if (!has_ldm || disk->partition)
+ continue;
+ sector = grub_disk_native_sectors (disk);
+ if (sector == GRUB_DISK_SIZE_UNKNOWN)
+ continue;
+ sector--;
+ break;
+ /* FIXME: try the third copy. */
+ case 2:
+ sector = gpt_ldm_sector (disk);
+ if (!sector)
+ continue;
+ break;
+ }
+ err = grub_disk_read (disk, sector, 0,
+ sizeof(label), &label);
+ if (err)
+ return NULL;
+ if (grub_memcmp (label.magic, LDM_MAGIC, sizeof (label.magic)) == 0
+ && grub_be_to_cpu16 (label.ver_major) == 0x02
+ && grub_be_to_cpu16 (label.ver_minor) >= 0x0b
+ && grub_be_to_cpu16 (label.ver_minor) <= 0x0c)
+ break;
+ }
+
+ /* Return if we didn't find a label. */
+ if (i == 3)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("no LDM signature found");
+#endif
+ return NULL;
+ }
+ }
+
+ id->uuid = grub_malloc (LDM_GUID_STRLEN + 1);
+ if (!id->uuid)
+ return NULL;
+ grub_memcpy (id->uuid, label.disk_guid, LDM_GUID_STRLEN);
+ id->uuid[LDM_GUID_STRLEN] = 0;
+ id->uuidlen = grub_strlen ((char *) id->uuid);
+ *start_sector = grub_be_to_cpu64 (label.pv_start);
+
+ {
+ grub_size_t s;
+ for (s = 0; s < LDM_GUID_STRLEN && label.group_guid[s]; s++);
+ vg = grub_diskfilter_get_vg_by_uuid (s, label.group_guid);
+ if (! vg)
+ vg = make_vg (disk, &label);
+ }
+
+ if (!vg)
+ {
+ grub_free (id->uuid);
+ return NULL;
+ }
+ return vg;
+}
+
+#ifdef GRUB_UTIL
+
+char *
+grub_util_get_ldm (grub_disk_t disk, grub_disk_addr_t start)
+{
+ struct grub_diskfilter_pv *pv = NULL;
+ struct grub_diskfilter_vg *vg = NULL;
+ struct grub_diskfilter_lv *res = 0, *lv, *res_lv = 0;
+
+ pv = grub_diskfilter_get_pv_from_disk (disk, &vg);
+
+ if (!pv)
+ return NULL;
+
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (lv->segment_count == 1 && lv->segments->node_count == 1
+ && lv->segments->type == GRUB_DISKFILTER_STRIPED
+ && lv->segments->nodes->pv == pv
+ && lv->segments->nodes->start + pv->start_sector == start)
+ {
+ res_lv = lv;
+ break;
+ }
+ if (!res_lv)
+ return NULL;
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (lv->segment_count == 1 && lv->segments->node_count == 1
+ && lv->segments->type == GRUB_DISKFILTER_MIRROR
+ && lv->segments->nodes->lv == res_lv)
+ {
+ res = lv;
+ break;
+ }
+ if (res && res->fullname)
+ return grub_strdup (res->fullname);
+ return NULL;
+}
+
+int
+grub_util_is_ldm (grub_disk_t disk)
+{
+ int i;
+ int has_ldm = msdos_has_ldm_partition (disk);
+ for (i = 0; i < 3; i++)
+ {
+ grub_disk_addr_t sector = LDM_LABEL_SECTOR;
+ grub_err_t err;
+ struct grub_ldm_label label;
+
+ switch (i)
+ {
+ case 0:
+ if (!has_ldm)
+ continue;
+ sector = LDM_LABEL_SECTOR;
+ break;
+ case 1:
+ /* LDM is never inside a partition. */
+ if (!has_ldm || disk->partition)
+ continue;
+ sector = grub_disk_native_sectors (disk);
+ if (sector == GRUB_DISK_SIZE_UNKNOWN)
+ continue;
+ sector--;
+ break;
+ /* FIXME: try the third copy. */
+ case 2:
+ sector = gpt_ldm_sector (disk);
+ if (!sector)
+ continue;
+ break;
+ }
+ err = grub_disk_read (disk, sector, 0, sizeof(label), &label);
+ if (err)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return 0;
+ }
+ /* This check is more relaxed on purpose. */
+ if (grub_memcmp (label.magic, LDM_MAGIC, sizeof (label.magic)) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+grub_err_t
+grub_util_ldm_embed (struct grub_disk *disk, unsigned int *nsectors,
+ unsigned int max_nsectors,
+ grub_embed_type_t embed_type,
+ grub_disk_addr_t **sectors)
+{
+ struct grub_diskfilter_pv *pv = NULL;
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_lv *lv;
+ unsigned i;
+
+ if (embed_type != GRUB_EMBED_PCBIOS)
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "LDM currently supports only PC-BIOS embedding");
+ if (disk->partition)
+ return grub_error (GRUB_ERR_BUG, "disk isn't LDM");
+ pv = grub_diskfilter_get_pv_from_disk (disk, &vg);
+ if (!pv)
+ return grub_error (GRUB_ERR_BUG, "disk isn't LDM");
+ for (lv = vg->lvs; lv; lv = lv->next)
+ {
+ struct grub_diskfilter_lv *comp;
+
+ if (!lv->visible || !lv->fullname)
+ continue;
+
+ if (lv->segment_count != 1)
+ continue;
+ if (lv->segments->type != GRUB_DISKFILTER_MIRROR
+ || lv->segments->node_count != 1
+ || lv->segments->start_extent != 0
+ || lv->segments->extent_count != lv->size)
+ continue;
+
+ comp = lv->segments->nodes->lv;
+ if (!comp)
+ continue;
+
+ if (comp->segment_count != 1 || comp->size != lv->size)
+ continue;
+ if (comp->segments->type != GRUB_DISKFILTER_STRIPED
+ || comp->segments->node_count != 1
+ || comp->segments->start_extent != 0
+ || comp->segments->extent_count != lv->size)
+ continue;
+
+ /* How to implement proper check is to be discussed. */
+#if 1
+ if (1)
+ continue;
+#else
+ if (grub_strcmp (lv->name, "Volume5") != 0)
+ continue;
+#endif
+ if (lv->size < *nsectors)
+ return grub_error (GRUB_ERR_OUT_OF_RANGE,
+ /* TRANSLATORS: it's a partition for embedding,
+ not a partition embed into something. GRUB
+ install tools put core.img into a place
+ usable for bootloaders (called generically
+ "embedding zone") and this operation is
+ called "embedding". */
+ N_("your LDM Embedding Partition is too small;"
+ " embedding won't be possible"));
+ *nsectors = lv->size;
+ if (*nsectors > max_nsectors)
+ *nsectors = max_nsectors;
+ *sectors = grub_calloc (*nsectors, sizeof (**sectors));
+ if (!*sectors)
+ return grub_errno;
+ for (i = 0; i < *nsectors; i++)
+ (*sectors)[i] = (lv->segments->nodes->start
+ + comp->segments->nodes->start
+ + comp->segments->nodes->pv->start_sector + i);
+ return GRUB_ERR_NONE;
+ }
+
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND,
+ /* TRANSLATORS: it's a partition for embedding,
+ not a partition embed into something. */
+ N_("this LDM has no Embedding Partition;"
+ " embedding won't be possible"));
+}
+#endif
+
+static struct grub_diskfilter grub_ldm_dev = {
+ .name = "ldm",
+ .detect = grub_ldm_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT (ldm)
+{
+ grub_diskfilter_register_back (&grub_ldm_dev);
+}
+
+GRUB_MOD_FINI (ldm)
+{
+ grub_diskfilter_unregister (&grub_ldm_dev);
+}
diff --git a/grub-core/disk/loopback.c b/grub-core/disk/loopback.c
new file mode 100644
index 0000000..41bebd1
--- /dev/null
+++ b/grub-core/disk/loopback.c
@@ -0,0 +1,240 @@
+/* loopback.c - command to add loopback devices. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2005,2006,2007 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/file.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct grub_loopback
+{
+ char *devname;
+ grub_file_t file;
+ struct grub_loopback *next;
+ unsigned long id;
+};
+
+static struct grub_loopback *loopback_list;
+static unsigned long last_id = 0;
+
+static const struct grub_arg_option options[] =
+ {
+ /* TRANSLATORS: The disk is simply removed from the list of available ones,
+ not wiped, avoid to scare user. */
+ {"delete", 'd', 0, N_("Delete the specified loopback drive."), 0, 0},
+ {0, 0, 0, 0, 0, 0}
+ };
+
+/* Delete the loopback device NAME. */
+static grub_err_t
+delete_loopback (const char *name)
+{
+ struct grub_loopback *dev;
+ struct grub_loopback **prev;
+
+ /* Search for the device. */
+ for (dev = loopback_list, prev = &loopback_list;
+ dev;
+ prev = &dev->next, dev = dev->next)
+ if (grub_strcmp (dev->devname, name) == 0)
+ break;
+
+ if (! dev)
+ return grub_error (GRUB_ERR_BAD_DEVICE, "device not found");
+
+ /* Remove the device from the list. */
+ *prev = dev->next;
+
+ grub_free (dev->devname);
+ grub_file_close (dev->file);
+ grub_free (dev);
+
+ return 0;
+}
+
+/* The command to add and remove loopback devices. */
+static grub_err_t
+grub_cmd_loopback (grub_extcmd_context_t ctxt, int argc, char **args)
+{
+ struct grub_arg_list *state = ctxt->state;
+ grub_file_t file;
+ struct grub_loopback *newdev;
+ grub_err_t ret;
+
+ if (argc < 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name required");
+
+ /* Check if `-d' was used. */
+ if (state[0].set)
+ return delete_loopback (args[0]);
+
+ if (argc < 2)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
+
+ /* Check that a device with requested name does not already exist. */
+ for (newdev = loopback_list; newdev; newdev = newdev->next)
+ if (grub_strcmp (newdev->devname, args[0]) == 0)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "device name already exists");
+
+ file = grub_file_open (args[1], GRUB_FILE_TYPE_LOOPBACK
+ | GRUB_FILE_TYPE_NO_DECOMPRESS);
+ if (! file)
+ return grub_errno;
+
+ /* 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;
+ newdev->id = last_id++;
+
+ /* 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 (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_loopback *d;
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+ for (d = loopback_list; d; d = d->next)
+ {
+ if (hook (d->devname, hook_data))
+ return 1;
+ }
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_open (const char *name, grub_disk_t disk)
+{
+ struct grub_loopback *dev;
+
+ for (dev = loopback_list; dev; dev = dev->next)
+ if (grub_strcmp (dev->devname, name) == 0)
+ break;
+
+ if (! dev)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
+
+ /* Use the filesize for the disk size, round up to a complete sector. */
+ if (dev->file->size != GRUB_FILE_SIZE_UNKNOWN)
+ disk->total_sectors = ((dev->file->size + GRUB_DISK_SECTOR_SIZE - 1)
+ / GRUB_DISK_SECTOR_SIZE);
+ else
+ disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+ /* Avoid reading more than 512M. */
+ disk->max_agglomerate = 1 << (29 - GRUB_DISK_SECTOR_BITS
+ - GRUB_DISK_CACHE_BITS);
+
+ disk->id = dev->id;
+
+ disk->data = dev;
+
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_file_t file = ((struct grub_loopback *) disk->data)->file;
+ grub_off_t pos;
+
+ grub_file_seek (file, sector << GRUB_DISK_SECTOR_BITS);
+
+ grub_file_read (file, buf, size << GRUB_DISK_SECTOR_BITS);
+ if (grub_errno)
+ return grub_errno;
+
+ /* In case there is more data read than there is available, in case
+ of files that are not a multiple of GRUB_DISK_SECTOR_SIZE, fill
+ the rest with zeros. */
+ pos = (sector + size) << GRUB_DISK_SECTOR_BITS;
+ if (pos > file->size)
+ {
+ grub_size_t amount = pos - file->size;
+ grub_memset (buf + (size << GRUB_DISK_SECTOR_BITS) - amount, 0, amount);
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_loopback_write (grub_disk_t disk __attribute ((unused)),
+ grub_disk_addr_t sector __attribute ((unused)),
+ grub_size_t size __attribute ((unused)),
+ const char *buf __attribute ((unused)))
+{
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "loopback write is not supported");
+}
+
+static struct grub_disk_dev grub_loopback_dev =
+ {
+ .name = "loopback",
+ .id = GRUB_DISK_DEVICE_LOOPBACK_ID,
+ .disk_iterate = grub_loopback_iterate,
+ .disk_open = grub_loopback_open,
+ .disk_read = grub_loopback_read,
+ .disk_write = grub_loopback_write,
+ .next = 0
+ };
+
+static grub_extcmd_t cmd;
+
+GRUB_MOD_INIT(loopback)
+{
+ cmd = grub_register_extcmd ("loopback", grub_cmd_loopback, 0,
+ N_("[-d] DEVICENAME FILE."),
+ /* TRANSLATORS: The file itself is not destroyed
+ or transformed into drive. */
+ N_("Make a virtual drive from a file."), options);
+ grub_disk_dev_register (&grub_loopback_dev);
+}
+
+GRUB_MOD_FINI(loopback)
+{
+ grub_unregister_extcmd (cmd);
+ grub_disk_dev_unregister (&grub_loopback_dev);
+}
diff --git a/grub-core/disk/luks.c b/grub-core/disk/luks.c
new file mode 100644
index 0000000..13103ea
--- /dev/null
+++ b/grub-core/disk/luks.c
@@ -0,0 +1,329 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2003,2007,2010,2011,2019 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/cryptodisk.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/err.h>
+#include <grub/disk.h>
+#include <grub/crypto.h>
+#include <grub/partition.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define MAX_PASSPHRASE 256
+
+#define LUKS_KEY_ENABLED 0x00AC71F3
+
+/* On disk LUKS header */
+struct grub_luks_phdr
+{
+ grub_uint8_t magic[6];
+#define LUKS_MAGIC "LUKS\xBA\xBE"
+ grub_uint16_t version;
+ char cipherName[32];
+ char cipherMode[32];
+ char hashSpec[32];
+ grub_uint32_t payloadOffset;
+ grub_uint32_t keyBytes;
+ grub_uint8_t mkDigest[20];
+ grub_uint8_t mkDigestSalt[32];
+ grub_uint32_t mkDigestIterations;
+ char uuid[40];
+ struct
+ {
+ grub_uint32_t active;
+ grub_uint32_t passwordIterations;
+ grub_uint8_t passwordSalt[32];
+ grub_uint32_t keyMaterialOffset;
+ grub_uint32_t stripes;
+ } keyblock[8];
+} GRUB_PACKED;
+
+typedef struct grub_luks_phdr *grub_luks_phdr_t;
+
+gcry_err_code_t AF_merge (const gcry_md_spec_t * hash, grub_uint8_t * src,
+ grub_uint8_t * dst, grub_size_t blocksize,
+ grub_size_t blocknumbers);
+
+static grub_cryptodisk_t
+configure_ciphers (grub_disk_t disk, const char *check_uuid,
+ int check_boot)
+{
+ grub_cryptodisk_t newdev;
+ const char *iptr;
+ struct grub_luks_phdr header;
+ char *optr;
+ char uuid[sizeof (header.uuid) + 1];
+ char ciphername[sizeof (header.cipherName) + 1];
+ char ciphermode[sizeof (header.cipherMode) + 1];
+ char hashspec[sizeof (header.hashSpec) + 1];
+ grub_err_t err;
+
+ if (check_boot)
+ return NULL;
+
+ /* Read the LUKS header. */
+ err = grub_disk_read (disk, 0, 0, sizeof (header), &header);
+ if (err)
+ {
+ if (err == GRUB_ERR_OUT_OF_RANGE)
+ grub_errno = GRUB_ERR_NONE;
+ return NULL;
+ }
+
+ /* Look for LUKS magic sequence. */
+ if (grub_memcmp (header.magic, LUKS_MAGIC, sizeof (header.magic))
+ || grub_be_to_cpu16 (header.version) != 1)
+ return NULL;
+
+ grub_memset (uuid, 0, sizeof (uuid));
+ optr = uuid;
+ for (iptr = header.uuid; iptr < &header.uuid[ARRAY_SIZE (header.uuid)];
+ iptr++)
+ {
+ if (*iptr != '-')
+ *optr++ = *iptr;
+ }
+ *optr = 0;
+
+ if (check_uuid && grub_strcasecmp (check_uuid, uuid) != 0)
+ {
+ grub_dprintf ("luks", "%s != %s\n", uuid, check_uuid);
+ return NULL;
+ }
+
+ /* Make sure that strings are null terminated. */
+ grub_memcpy (ciphername, header.cipherName, sizeof (header.cipherName));
+ ciphername[sizeof (header.cipherName)] = 0;
+ grub_memcpy (ciphermode, header.cipherMode, sizeof (header.cipherMode));
+ ciphermode[sizeof (header.cipherMode)] = 0;
+ grub_memcpy (hashspec, header.hashSpec, sizeof (header.hashSpec));
+ hashspec[sizeof (header.hashSpec)] = 0;
+
+ newdev = grub_zalloc (sizeof (struct grub_cryptodisk));
+ if (!newdev)
+ return NULL;
+ newdev->offset_sectors = grub_be_to_cpu32 (header.payloadOffset);
+ newdev->source_disk = NULL;
+ newdev->log_sector_size = GRUB_LUKS1_LOG_SECTOR_SIZE;
+ newdev->total_sectors = grub_disk_native_sectors (disk) - newdev->offset_sectors;
+ grub_memcpy (newdev->uuid, uuid, sizeof (uuid));
+ newdev->modname = "luks";
+
+ /* Configure the hash used for the AF splitter and HMAC. */
+ newdev->hash = grub_crypto_lookup_md_by_name (hashspec);
+ if (!newdev->hash)
+ {
+ grub_free (newdev);
+ grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+ hashspec);
+ return NULL;
+ }
+
+ err = grub_cryptodisk_setcipher (newdev, ciphername, ciphermode);
+ if (err)
+ {
+ grub_free (newdev);
+ return NULL;
+ }
+
+ COMPILE_TIME_ASSERT (sizeof (newdev->uuid) >= sizeof (uuid));
+ return newdev;
+}
+
+static grub_err_t
+luks_recover_key (grub_disk_t source,
+ grub_cryptodisk_t dev)
+{
+ struct grub_luks_phdr header;
+ grub_size_t keysize;
+ grub_uint8_t *split_key = NULL;
+ char passphrase[MAX_PASSPHRASE] = "";
+ grub_uint8_t candidate_digest[sizeof (header.mkDigest)];
+ unsigned i;
+ grub_size_t length;
+ grub_err_t err;
+ grub_size_t max_stripes = 1;
+ char *tmp;
+
+ err = grub_disk_read (source, 0, 0, sizeof (header), &header);
+ if (err)
+ return err;
+
+ grub_puts_ (N_("Attempting to decrypt master key..."));
+ keysize = grub_be_to_cpu32 (header.keyBytes);
+ if (keysize > GRUB_CRYPTODISK_MAX_KEYLEN)
+ return grub_error (GRUB_ERR_BAD_FS, "key is too long");
+
+ for (i = 0; i < ARRAY_SIZE (header.keyblock); i++)
+ if (grub_be_to_cpu32 (header.keyblock[i].active) == LUKS_KEY_ENABLED
+ && grub_be_to_cpu32 (header.keyblock[i].stripes) > max_stripes)
+ max_stripes = grub_be_to_cpu32 (header.keyblock[i].stripes);
+
+ split_key = grub_calloc (keysize, max_stripes);
+ if (!split_key)
+ return grub_errno;
+
+ /* Get the passphrase from the user. */
+ tmp = NULL;
+ if (source->partition)
+ tmp = grub_partition_get_name (source->partition);
+ grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
+ source->partition ? "," : "", tmp ? : "",
+ dev->uuid);
+ grub_free (tmp);
+ if (!grub_password_get (passphrase, MAX_PASSPHRASE))
+ {
+ grub_free (split_key);
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+ }
+
+ /* Try to recover master key from each active keyslot. */
+ for (i = 0; i < ARRAY_SIZE (header.keyblock); i++)
+ {
+ gcry_err_code_t gcry_err;
+ grub_uint8_t candidate_key[GRUB_CRYPTODISK_MAX_KEYLEN];
+ grub_uint8_t digest[GRUB_CRYPTODISK_MAX_KEYLEN];
+
+ /* Check if keyslot is enabled. */
+ if (grub_be_to_cpu32 (header.keyblock[i].active) != LUKS_KEY_ENABLED)
+ continue;
+
+ grub_dprintf ("luks", "Trying keyslot %d\n", i);
+
+ /* Calculate the PBKDF2 of the user supplied passphrase. */
+ gcry_err = grub_crypto_pbkdf2 (dev->hash, (grub_uint8_t *) passphrase,
+ grub_strlen (passphrase),
+ header.keyblock[i].passwordSalt,
+ sizeof (header.keyblock[i].passwordSalt),
+ grub_be_to_cpu32 (header.keyblock[i].
+ passwordIterations),
+ digest, keysize);
+
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ grub_dprintf ("luks", "PBKDF2 done\n");
+
+ gcry_err = grub_cryptodisk_setkey (dev, digest, keysize);
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ length = (keysize * grub_be_to_cpu32 (header.keyblock[i].stripes));
+
+ /* Read and decrypt the key material from the disk. */
+ err = grub_disk_read (source,
+ grub_be_to_cpu32 (header.keyblock
+ [i].keyMaterialOffset), 0,
+ length, split_key);
+ if (err)
+ {
+ grub_free (split_key);
+ return err;
+ }
+
+ gcry_err = grub_cryptodisk_decrypt (dev, split_key, length, 0,
+ GRUB_LUKS1_LOG_SECTOR_SIZE);
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ /* Merge the decrypted key material to get the candidate master key. */
+ gcry_err = AF_merge (dev->hash, split_key, candidate_key, keysize,
+ grub_be_to_cpu32 (header.keyblock[i].stripes));
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ grub_dprintf ("luks", "candidate key recovered\n");
+
+ /* Calculate the PBKDF2 of the candidate master key. */
+ gcry_err = grub_crypto_pbkdf2 (dev->hash, candidate_key,
+ grub_be_to_cpu32 (header.keyBytes),
+ header.mkDigestSalt,
+ sizeof (header.mkDigestSalt),
+ grub_be_to_cpu32
+ (header.mkDigestIterations),
+ candidate_digest,
+ sizeof (candidate_digest));
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ /* Compare the calculated PBKDF2 to the digest stored
+ in the header to see if it's correct. */
+ if (grub_memcmp (candidate_digest, header.mkDigest,
+ sizeof (header.mkDigest)) != 0)
+ {
+ grub_dprintf ("luks", "bad digest\n");
+ continue;
+ }
+
+ /* TRANSLATORS: It's a cryptographic key slot: one element of an array
+ where each element is either empty or holds a key. */
+ grub_printf_ (N_("Slot %d opened\n"), i);
+
+ /* Set the master key. */
+ gcry_err = grub_cryptodisk_setkey (dev, candidate_key, keysize);
+ if (gcry_err)
+ {
+ grub_free (split_key);
+ return grub_crypto_gcry_error (gcry_err);
+ }
+
+ grub_free (split_key);
+
+ return GRUB_ERR_NONE;
+ }
+
+ grub_free (split_key);
+ return GRUB_ACCESS_DENIED;
+}
+
+struct grub_cryptodisk_dev luks_crypto = {
+ .scan = configure_ciphers,
+ .recover_key = luks_recover_key
+};
+
+GRUB_MOD_INIT (luks)
+{
+ COMPILE_TIME_ASSERT (sizeof (((struct grub_luks_phdr *) 0)->uuid)
+ < GRUB_CRYPTODISK_MAX_UUID_LENGTH);
+ grub_cryptodisk_dev_register (&luks_crypto);
+}
+
+GRUB_MOD_FINI (luks)
+{
+ grub_cryptodisk_dev_unregister (&luks_crypto);
+}
diff --git a/grub-core/disk/luks2.c b/grub-core/disk/luks2.c
new file mode 100644
index 0000000..371a53b
--- /dev/null
+++ b/grub-core/disk/luks2.c
@@ -0,0 +1,791 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2019 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/cryptodisk.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/dl.h>
+#include <grub/err.h>
+#include <grub/disk.h>
+#include <grub/crypto.h>
+#include <grub/partition.h>
+#include <grub/i18n.h>
+
+#include <base64.h>
+#include <json.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define LUKS_MAGIC_1ST "LUKS\xBA\xBE"
+#define LUKS_MAGIC_2ND "SKUL\xBA\xBE"
+
+#define MAX_PASSPHRASE 256
+
+enum grub_luks2_kdf_type
+{
+ LUKS2_KDF_TYPE_ARGON2I,
+ LUKS2_KDF_TYPE_PBKDF2
+};
+typedef enum grub_luks2_kdf_type grub_luks2_kdf_type_t;
+
+/* On disk LUKS header */
+struct grub_luks2_header
+{
+ char magic[6];
+ grub_uint16_t version;
+ grub_uint64_t hdr_size;
+ grub_uint64_t seqid;
+ char label[48];
+ char csum_alg[32];
+ grub_uint8_t salt[64];
+ char uuid[40];
+ char subsystem[48];
+ grub_uint64_t hdr_offset;
+ char _padding[184];
+ grub_uint8_t csum[64];
+ char _padding4096[7*512];
+} GRUB_PACKED;
+typedef struct grub_luks2_header grub_luks2_header_t;
+
+struct grub_luks2_keyslot
+{
+ /* The integer key to the associative array of keyslots. */
+ grub_uint64_t idx;
+ grub_int64_t key_size;
+ grub_int64_t priority;
+ struct
+ {
+ const char *encryption;
+ grub_uint64_t offset;
+ grub_uint64_t size;
+ grub_int64_t key_size;
+ } area;
+ struct
+ {
+ const char *hash;
+ grub_int64_t stripes;
+ } af;
+ struct
+ {
+ grub_luks2_kdf_type_t type;
+ const char *salt;
+ union
+ {
+ struct
+ {
+ grub_int64_t time;
+ grub_int64_t memory;
+ grub_int64_t cpus;
+ } argon2i;
+ struct
+ {
+ const char *hash;
+ grub_int64_t iterations;
+ } pbkdf2;
+ } u;
+ } kdf;
+};
+typedef struct grub_luks2_keyslot grub_luks2_keyslot_t;
+
+struct grub_luks2_segment
+{
+ grub_uint64_t idx;
+ grub_uint64_t offset;
+ const char *size;
+ const char *encryption;
+ grub_int64_t sector_size;
+};
+typedef struct grub_luks2_segment grub_luks2_segment_t;
+
+struct grub_luks2_digest
+{
+ grub_uint64_t idx;
+ /* Both keyslots and segments are interpreted as bitfields here */
+ grub_uint64_t keyslots;
+ grub_uint64_t segments;
+ const char *salt;
+ const char *digest;
+ const char *hash;
+ grub_int64_t iterations;
+};
+typedef struct grub_luks2_digest grub_luks2_digest_t;
+
+gcry_err_code_t AF_merge (const gcry_md_spec_t * hash, grub_uint8_t * src,
+ grub_uint8_t * dst, grub_size_t blocksize,
+ grub_size_t blocknumbers);
+
+static grub_err_t
+luks2_parse_keyslot (grub_luks2_keyslot_t *out, const grub_json_t *keyslot)
+{
+ grub_json_t area, af, kdf;
+ const char *type;
+
+ if (grub_json_getstring (&type, keyslot, "type"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid keyslot");
+ else if (grub_strcmp (type, "luks2"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported keyslot type %s", type);
+ else if (grub_json_getint64 (&out->key_size, keyslot, "key_size"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing keyslot information");
+ if (grub_json_getint64 (&out->priority, keyslot, "priority"))
+ out->priority = 1;
+
+ if (grub_json_getvalue (&area, keyslot, "area") ||
+ grub_json_getstring (&type, &area, "type"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid key area");
+ else if (grub_strcmp (type, "raw"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported key area type: %s", type);
+ else if (grub_json_getuint64 (&out->area.offset, &area, "offset") ||
+ grub_json_getuint64 (&out->area.size, &area, "size") ||
+ grub_json_getstring (&out->area.encryption, &area, "encryption") ||
+ grub_json_getint64 (&out->area.key_size, &area, "key_size"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing key area information");
+
+ if (grub_json_getvalue (&kdf, keyslot, "kdf") ||
+ grub_json_getstring (&type, &kdf, "type") ||
+ grub_json_getstring (&out->kdf.salt, &kdf, "salt"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing or invalid KDF");
+ else if (!grub_strcmp (type, "argon2i") || !grub_strcmp (type, "argon2id"))
+ {
+ out->kdf.type = LUKS2_KDF_TYPE_ARGON2I;
+ if (grub_json_getint64 (&out->kdf.u.argon2i.time, &kdf, "time") ||
+ grub_json_getint64 (&out->kdf.u.argon2i.memory, &kdf, "memory") ||
+ grub_json_getint64 (&out->kdf.u.argon2i.cpus, &kdf, "cpus"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing Argon2i parameters");
+ }
+ else if (!grub_strcmp (type, "pbkdf2"))
+ {
+ out->kdf.type = LUKS2_KDF_TYPE_PBKDF2;
+ if (grub_json_getstring (&out->kdf.u.pbkdf2.hash, &kdf, "hash") ||
+ grub_json_getint64 (&out->kdf.u.pbkdf2.iterations, &kdf, "iterations"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing PBKDF2 parameters");
+ }
+ else
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported KDF type %s", type);
+
+ if (grub_json_getvalue (&af, keyslot, "af") ||
+ grub_json_getstring (&type, &af, "type"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "missing or invalid area");
+ if (grub_strcmp (type, "luks1"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported AF type %s", type);
+ if (grub_json_getint64 (&out->af.stripes, &af, "stripes") ||
+ grub_json_getstring (&out->af.hash, &af, "hash"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing AF parameters");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_parse_segment (grub_luks2_segment_t *out, const grub_json_t *segment)
+{
+ const char *type;
+
+ if (grub_json_getstring (&type, segment, "type"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment type");
+ else if (grub_strcmp (type, "crypt"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported segment type %s", type);
+
+ if (grub_json_getuint64 (&out->offset, segment, "offset") ||
+ grub_json_getstring (&out->size, segment, "size") ||
+ grub_json_getstring (&out->encryption, segment, "encryption") ||
+ grub_json_getint64 (&out->sector_size, segment, "sector_size"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing segment parameters");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_parse_digest (grub_luks2_digest_t *out, const grub_json_t *digest)
+{
+ grub_json_t segments, keyslots, o;
+ grub_size_t i, size;
+ grub_uint64_t bit;
+ const char *type;
+
+ if (grub_json_getstring (&type, digest, "type"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest type");
+ else if (grub_strcmp (type, "pbkdf2"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Unsupported digest type %s", type);
+
+ if (grub_json_getvalue (&segments, digest, "segments") ||
+ grub_json_getvalue (&keyslots, digest, "keyslots") ||
+ grub_json_getstring (&out->salt, digest, "salt") ||
+ grub_json_getstring (&out->digest, digest, "digest") ||
+ grub_json_getstring (&out->hash, digest, "hash") ||
+ grub_json_getint64 (&out->iterations, digest, "iterations"))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Missing digest parameters");
+
+ if (grub_json_getsize (&size, &segments))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Digest references no segments");
+
+ out->segments = 0;
+ for (i = 0; i < size; i++)
+ {
+ if (grub_json_getchild (&o, &segments, i) ||
+ grub_json_getuint64 (&bit, &o, NULL))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid segment");
+ out->segments |= (1 << bit);
+ }
+
+ if (grub_json_getsize (&size, &keyslots))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Digest references no keyslots");
+
+ out->keyslots = 0;
+ for (i = 0; i < size; i++)
+ {
+ if (grub_json_getchild (&o, &keyslots, i) ||
+ grub_json_getuint64 (&bit, &o, NULL))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot");
+ out->keyslots |= (1 << bit);
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_get_keyslot (grub_luks2_keyslot_t *k, grub_luks2_digest_t *d, grub_luks2_segment_t *s,
+ const grub_json_t *root, grub_size_t keyslot_json_idx)
+{
+ grub_json_t keyslots, keyslot, digests, digest, segments, segment;
+ grub_size_t json_idx, size;
+
+ /* Get nth keyslot */
+ if (grub_json_getvalue (&keyslots, root, "keyslots") ||
+ grub_json_getchild (&keyslot, &keyslots, keyslot_json_idx) ||
+ grub_json_getuint64 (&k->idx, &keyslot, NULL) ||
+ grub_json_getchild (&keyslot, &keyslot, 0) ||
+ luks2_parse_keyslot (k, &keyslot))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse keyslot index %" PRIuGRUB_SIZE, keyslot_json_idx);
+
+ /* Get digest that matches the keyslot. */
+ if (grub_json_getvalue (&digests, root, "digests") ||
+ grub_json_getsize (&size, &digests))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get digests");
+ for (json_idx = 0; json_idx < size; json_idx++)
+ {
+ if (grub_json_getchild (&digest, &digests, json_idx) ||
+ grub_json_getuint64 (&d->idx, &digest, NULL) ||
+ grub_json_getchild (&digest, &digest, 0) ||
+ luks2_parse_digest (d, &digest))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse digest index %" PRIuGRUB_SIZE, json_idx);
+
+ if ((d->keyslots & (1 << k->idx)))
+ break;
+ }
+ if (json_idx == size)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No digest for keyslot \"%" PRIuGRUB_UINT64_T "\"", k->idx);
+
+ /* Get segment that matches the digest. */
+ if (grub_json_getvalue (&segments, root, "segments") ||
+ grub_json_getsize (&size, &segments))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get segments");
+ for (json_idx = 0; json_idx < size; json_idx++)
+ {
+ if (grub_json_getchild (&segment, &segments, json_idx) ||
+ grub_json_getuint64 (&s->idx, &segment, NULL) ||
+ grub_json_getchild (&segment, &segment, 0) ||
+ luks2_parse_segment (s, &segment))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not parse segment index %" PRIuGRUB_SIZE, json_idx);
+
+ if ((d->segments & (1 << s->idx)))
+ break;
+ }
+ if (json_idx == size)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "No segment for digest \"%" PRIuGRUB_UINT64_T "\"", d->idx);
+
+ return GRUB_ERR_NONE;
+}
+
+/* Determine whether to use primary or secondary header */
+static grub_err_t
+luks2_read_header (grub_disk_t disk, grub_luks2_header_t *outhdr)
+{
+ grub_luks2_header_t primary, secondary, *header = &primary;
+ grub_err_t ret;
+
+ /* Read the primary LUKS header. */
+ ret = grub_disk_read (disk, 0, 0, sizeof (primary), &primary);
+ if (ret)
+ return ret;
+
+ /* Look for LUKS magic sequence. */
+ if (grub_memcmp (primary.magic, LUKS_MAGIC_1ST, sizeof (primary.magic)) ||
+ grub_be_to_cpu16 (primary.version) != 2)
+ return GRUB_ERR_BAD_SIGNATURE;
+
+ /* Read the secondary header. */
+ ret = grub_disk_read (disk, 0, grub_be_to_cpu64 (primary.hdr_size), sizeof (secondary), &secondary);
+ if (ret)
+ return ret;
+
+ /* Look for LUKS magic sequence. */
+ if (grub_memcmp (secondary.magic, LUKS_MAGIC_2ND, sizeof (secondary.magic)) ||
+ grub_be_to_cpu16 (secondary.version) != 2)
+ return GRUB_ERR_BAD_SIGNATURE;
+
+ if (grub_be_to_cpu64 (primary.seqid) < grub_be_to_cpu64 (secondary.seqid))
+ header = &secondary;
+ grub_memcpy (outhdr, header, sizeof (*header));
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_cryptodisk_t
+luks2_scan (grub_disk_t disk, const char *check_uuid, int check_boot)
+{
+ grub_cryptodisk_t cryptodisk;
+ grub_luks2_header_t header;
+ char uuid[sizeof (header.uuid) + 1];
+ grub_size_t i, j;
+
+ if (check_boot)
+ return NULL;
+
+ if (luks2_read_header (disk, &header))
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return NULL;
+ }
+
+ for (i = 0, j = 0; i < sizeof (header.uuid); i++)
+ if (header.uuid[i] != '-')
+ uuid[j++] = header.uuid[i];
+ uuid[j] = '\0';
+
+ if (check_uuid && grub_strcasecmp (check_uuid, uuid) != 0)
+ return NULL;
+
+ cryptodisk = grub_zalloc (sizeof (*cryptodisk));
+ if (!cryptodisk)
+ return NULL;
+
+ COMPILE_TIME_ASSERT (sizeof (cryptodisk->uuid) >= sizeof (uuid));
+ grub_memcpy (cryptodisk->uuid, uuid, sizeof (uuid));
+
+ cryptodisk->modname = "luks2";
+ return cryptodisk;
+}
+
+static grub_err_t
+luks2_verify_key (grub_luks2_digest_t *d, grub_uint8_t *candidate_key,
+ grub_size_t candidate_key_len)
+{
+ grub_uint8_t candidate_digest[GRUB_CRYPTODISK_MAX_KEYLEN];
+ grub_uint8_t digest[GRUB_CRYPTODISK_MAX_KEYLEN], salt[GRUB_CRYPTODISK_MAX_KEYLEN];
+ grub_size_t saltlen = sizeof (salt), digestlen = sizeof (digest);
+ const gcry_md_spec_t *hash;
+ gcry_err_code_t gcry_ret;
+
+ /* Decode both digest and salt */
+ if (!base64_decode (d->digest, grub_strlen (d->digest), (char *)digest, &digestlen))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest");
+ if (!base64_decode (d->salt, grub_strlen (d->salt), (char *)salt, &saltlen))
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid digest salt");
+
+ /* Configure the hash used for the digest. */
+ hash = grub_crypto_lookup_md_by_name (d->hash);
+ if (!hash)
+ return grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash", d->hash);
+
+ /* Calculate the candidate key's digest */
+ gcry_ret = grub_crypto_pbkdf2 (hash,
+ candidate_key, candidate_key_len,
+ salt, saltlen,
+ d->iterations,
+ candidate_digest, digestlen);
+ if (gcry_ret)
+ return grub_crypto_gcry_error (gcry_ret);
+
+ if (grub_memcmp (candidate_digest, digest, digestlen) != 0)
+ return grub_error (GRUB_ERR_ACCESS_DENIED, "Mismatching digests");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+luks2_decrypt_key (grub_uint8_t *out_key,
+ grub_disk_t source, grub_cryptodisk_t crypt,
+ grub_luks2_keyslot_t *k,
+ const grub_uint8_t *passphrase, grub_size_t passphraselen)
+{
+ grub_uint8_t area_key[GRUB_CRYPTODISK_MAX_KEYLEN];
+ grub_uint8_t salt[GRUB_CRYPTODISK_MAX_KEYLEN];
+ grub_uint8_t *split_key = NULL;
+ grub_size_t saltlen = sizeof (salt);
+ char cipher[32], *p;
+ const gcry_md_spec_t *hash;
+ gcry_err_code_t gcry_ret;
+ grub_err_t ret;
+
+ if (!base64_decode (k->kdf.salt, grub_strlen (k->kdf.salt),
+ (char *)salt, &saltlen))
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid keyslot salt");
+ goto err;
+ }
+
+ /* Calculate the binary area key of the user supplied passphrase. */
+ switch (k->kdf.type)
+ {
+ case LUKS2_KDF_TYPE_ARGON2I:
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Argon2 not supported");
+ goto err;
+ case LUKS2_KDF_TYPE_PBKDF2:
+ hash = grub_crypto_lookup_md_by_name (k->kdf.u.pbkdf2.hash);
+ if (!hash)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+ k->kdf.u.pbkdf2.hash);
+ goto err;
+ }
+
+ gcry_ret = grub_crypto_pbkdf2 (hash, (grub_uint8_t *) passphrase,
+ passphraselen,
+ salt, saltlen,
+ k->kdf.u.pbkdf2.iterations,
+ area_key, k->area.key_size);
+ if (gcry_ret)
+ {
+ ret = grub_crypto_gcry_error (gcry_ret);
+ goto err;
+ }
+
+ break;
+ }
+
+ /* Set up disk encryption parameters for the key area */
+ grub_strncpy (cipher, k->area.encryption, sizeof (cipher));
+ p = grub_memchr (cipher, '-', grub_strlen (cipher));
+ if (!p)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
+ *p = '\0';
+
+ ret = grub_cryptodisk_setcipher (crypt, cipher, p + 1);
+ if (ret)
+ return ret;
+
+ gcry_ret = grub_cryptodisk_setkey (crypt, area_key, k->area.key_size);
+ if (gcry_ret)
+ {
+ ret = grub_crypto_gcry_error (gcry_ret);
+ goto err;
+ }
+
+ /* Read and decrypt the binary key area with the area key. */
+ split_key = grub_malloc (k->area.size);
+ if (!split_key)
+ {
+ ret = grub_errno;
+ goto err;
+ }
+
+ grub_errno = GRUB_ERR_NONE;
+ ret = grub_disk_read (source, 0, k->area.offset, k->area.size, split_key);
+ if (ret)
+ {
+ grub_error (GRUB_ERR_IO, "Read error: %s\n", grub_errmsg);
+ goto err;
+ }
+
+ /*
+ * The key slots area is always encrypted in 512-byte sectors,
+ * regardless of encrypted data sector size.
+ */
+ gcry_ret = grub_cryptodisk_decrypt (crypt, split_key, k->area.size, 0,
+ GRUB_LUKS1_LOG_SECTOR_SIZE);
+ if (gcry_ret)
+ {
+ ret = grub_crypto_gcry_error (gcry_ret);
+ goto err;
+ }
+
+ /* Configure the hash used for anti-forensic merging. */
+ hash = grub_crypto_lookup_md_by_name (k->af.hash);
+ if (!hash)
+ {
+ ret = grub_error (GRUB_ERR_FILE_NOT_FOUND, "Couldn't load %s hash",
+ k->af.hash);
+ goto err;
+ }
+
+ /* Merge the decrypted key material to get the candidate master key. */
+ gcry_ret = AF_merge (hash, split_key, out_key, k->key_size, k->af.stripes);
+ if (gcry_ret)
+ {
+ ret = grub_crypto_gcry_error (gcry_ret);
+ goto err;
+ }
+
+ grub_dprintf ("luks2", "Candidate key recovered\n");
+
+ err:
+ grub_free (split_key);
+ return ret;
+}
+
+static grub_err_t
+luks2_recover_key (grub_disk_t source,
+ grub_cryptodisk_t crypt)
+{
+ grub_uint8_t candidate_key[GRUB_CRYPTODISK_MAX_KEYLEN];
+ char passphrase[MAX_PASSPHRASE], cipher[32];
+ char *json_header = NULL, *part = NULL, *ptr;
+ grub_size_t candidate_key_len = 0, json_idx, size;
+ grub_luks2_header_t header;
+ grub_luks2_keyslot_t keyslot;
+ grub_luks2_digest_t digest;
+ grub_luks2_segment_t segment;
+ gcry_err_code_t gcry_ret;
+ grub_json_t *json = NULL, keyslots;
+ grub_err_t ret;
+
+ ret = luks2_read_header (source, &header);
+ if (ret)
+ return ret;
+
+ json_header = grub_zalloc (grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+ if (!json_header)
+ return GRUB_ERR_OUT_OF_MEMORY;
+
+ /* Read the JSON area. */
+ ret = grub_disk_read (source, 0, grub_be_to_cpu64 (header.hdr_offset) + sizeof (header),
+ grub_be_to_cpu64 (header.hdr_size) - sizeof (header), json_header);
+ if (ret)
+ goto err;
+
+ ptr = grub_memchr (json_header, 0, grub_be_to_cpu64 (header.hdr_size) - sizeof (header));
+ if (!ptr)
+ goto err;
+
+ ret = grub_json_parse (&json, json_header, grub_be_to_cpu64 (header.hdr_size));
+ if (ret)
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid LUKS2 JSON header");
+ goto err;
+ }
+
+ /* Get the passphrase from the user. */
+ if (source->partition)
+ part = grub_partition_get_name (source->partition);
+ grub_printf_ (N_("Enter passphrase for %s%s%s (%s): "), source->name,
+ source->partition ? "," : "", part ? : "",
+ crypt->uuid);
+ if (!grub_password_get (passphrase, MAX_PASSPHRASE))
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Passphrase not supplied");
+ goto err;
+ }
+
+ if (grub_json_getvalue (&keyslots, json, "keyslots") ||
+ grub_json_getsize (&size, &keyslots))
+ {
+ ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Could not get keyslots");
+ goto err;
+ }
+
+ if (grub_disk_native_sectors (source) == GRUB_DISK_SIZE_UNKNOWN)
+ {
+ /* FIXME: Allow use of source disk, and maybe cause errors in read. */
+ grub_dprintf ("luks2", "Source disk %s has an unknown size, "
+ "conservatively returning error\n", source->name);
+ ret = grub_error (GRUB_ERR_BUG, "Unknown size of luks2 source device");
+ goto err;
+ }
+
+ /* Try all keyslot */
+ for (json_idx = 0; json_idx < size; json_idx++)
+ {
+ char indexstr[21]; /* log10(2^64) ~ 20, plus NUL character. */
+ typeof (source->total_sectors) max_crypt_sectors = 0;
+
+ grub_errno = GRUB_ERR_NONE;
+ ret = luks2_get_keyslot (&keyslot, &digest, &segment, json, json_idx);
+ if (ret)
+ goto err;
+ if (grub_errno != GRUB_ERR_NONE)
+ grub_dprintf ("luks2", "Ignoring unhandled error %d from luks2_get_keyslot\n", grub_errno);
+
+ if (keyslot.priority == 0)
+ {
+ grub_dprintf ("luks2", "Ignoring keyslot \"%" PRIuGRUB_UINT64_T "\" due to priority\n", keyslot.idx);
+ continue;
+ }
+
+ grub_dprintf ("luks2", "Trying keyslot \"%" PRIuGRUB_UINT64_T "\"\n", keyslot.idx);
+
+ /* Sector size should be one of 512, 1024, 2048, or 4096. */
+ if (!(segment.sector_size == 512 || segment.sector_size == 1024 ||
+ segment.sector_size == 2048 || segment.sector_size == 4096))
+ {
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" sector"
+ " size %" PRIuGRUB_UINT64_T " is not one of"
+ " 512, 1024, 2048, or 4096\n",
+ segment.idx, segment.sector_size);
+ continue;
+ }
+
+ /* Set up disk according to keyslot's segment. */
+ crypt->offset_sectors = grub_divmod64 (segment.offset, segment.sector_size, NULL);
+ crypt->log_sector_size = grub_log2ull (segment.sector_size);
+ /* Set to the source disk/partition size, which is the maximum we allow. */
+ max_crypt_sectors = grub_disk_native_sectors (source);
+ max_crypt_sectors = grub_convert_sector (max_crypt_sectors, GRUB_DISK_SECTOR_BITS,
+ crypt->log_sector_size);
+
+ if (max_crypt_sectors < crypt->offset_sectors)
+ {
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has offset"
+ " %" PRIuGRUB_UINT64_T " which is greater than"
+ " source disk size %" PRIuGRUB_UINT64_T ","
+ " skipping\n", segment.idx, crypt->offset_sectors,
+ max_crypt_sectors);
+ continue;
+ }
+
+ if (grub_strcmp (segment.size, "dynamic") == 0)
+ crypt->total_sectors = max_crypt_sectors - crypt->offset_sectors;
+ else
+ {
+ grub_errno = GRUB_ERR_NONE;
+
+ /* Convert segment.size to sectors, rounding up to nearest sector */
+ crypt->total_sectors = grub_strtoull (segment.size, NULL, 10);
+
+ if (grub_errno == GRUB_ERR_NONE)
+ {
+ crypt->total_sectors = ALIGN_UP (crypt->total_sectors,
+ 1 << crypt->log_sector_size);
+ crypt->total_sectors >>= crypt->log_sector_size;
+ }
+ else if (grub_errno == GRUB_ERR_BAD_NUMBER)
+ {
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" size"
+ " \"%s\" is not a parsable number,"
+ " skipping keyslot\n",
+ segment.idx, segment.size);
+ continue;
+ }
+ else if (grub_errno == GRUB_ERR_OUT_OF_RANGE)
+ {
+ /*
+ * There was an overflow in parsing segment.size, so disk must
+ * be very large or the string is incorrect.
+ *
+ * TODO: Allow reading of at least up max_crypt_sectors. Really,
+ * its very unlikely one would be booting from such a large drive
+ * anyway. Use another smaller LUKS2 boot device.
+ */
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" size"
+ " %s overflowed 64-bit unsigned integer,"
+ " skipping keyslot\n", segment.idx, segment.size);
+ continue;
+ }
+ }
+
+ if (crypt->total_sectors == 0)
+ {
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has zero"
+ " sectors, skipping\n", segment.idx);
+ continue;
+ }
+ else if (max_crypt_sectors < (crypt->offset_sectors + crypt->total_sectors))
+ {
+ grub_dprintf ("luks2", "Segment \"%" PRIuGRUB_UINT64_T "\" has last"
+ " data position greater than source disk size,"
+ " the end of the crypto device will be"
+ " inaccessible\n", segment.idx);
+
+ /* Allow decryption up to the end of the source disk. */
+ crypt->total_sectors = max_crypt_sectors - crypt->offset_sectors;
+ }
+
+ ret = luks2_decrypt_key (candidate_key, source, crypt, &keyslot,
+ (const grub_uint8_t *) passphrase, grub_strlen (passphrase));
+ if (ret)
+ {
+ grub_dprintf ("luks2", "Decryption with keyslot \"%" PRIuGRUB_UINT64_T "\" failed: %s\n",
+ keyslot.idx, grub_errmsg);
+ continue;
+ }
+
+ ret = luks2_verify_key (&digest, candidate_key, keyslot.key_size);
+ if (ret)
+ {
+ grub_dprintf ("luks2", "Could not open keyslot \"%" PRIuGRUB_UINT64_T "\": %s\n",
+ keyslot.idx, grub_errmsg);
+ continue;
+ }
+
+ grub_snprintf (indexstr, sizeof (indexstr) - 1, "%" PRIuGRUB_UINT64_T, keyslot.idx);
+ /*
+ * TRANSLATORS: It's a cryptographic key slot: one element of an array
+ * where each element is either empty or holds a key.
+ */
+ grub_printf_ (N_("Slot \"%s\" opened\n"), indexstr);
+
+ candidate_key_len = keyslot.key_size;
+ break;
+ }
+ if (candidate_key_len == 0)
+ {
+ ret = grub_error (GRUB_ERR_ACCESS_DENIED, "Invalid passphrase");
+ goto err;
+ }
+
+ /* Set up disk cipher. */
+ grub_strncpy (cipher, segment.encryption, sizeof (cipher));
+ ptr = grub_memchr (cipher, '-', grub_strlen (cipher));
+ if (!ptr)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid encryption");
+ *ptr = '\0';
+
+ ret = grub_cryptodisk_setcipher (crypt, cipher, ptr + 1);
+ if (ret)
+ goto err;
+
+ /* Set the master key. */
+ gcry_ret = grub_cryptodisk_setkey (crypt, candidate_key, candidate_key_len);
+ if (gcry_ret)
+ {
+ ret = grub_crypto_gcry_error (gcry_ret);
+ goto err;
+ }
+
+ err:
+ grub_free (part);
+ grub_free (json_header);
+ grub_json_free (json);
+ return ret;
+}
+
+static struct grub_cryptodisk_dev luks2_crypto = {
+ .scan = luks2_scan,
+ .recover_key = luks2_recover_key
+};
+
+GRUB_MOD_INIT (luks2)
+{
+ grub_cryptodisk_dev_register (&luks2_crypto);
+}
+
+GRUB_MOD_FINI (luks2)
+{
+ grub_cryptodisk_dev_unregister (&luks2_crypto);
+}
diff --git a/grub-core/disk/lvm.c b/grub-core/disk/lvm.c
new file mode 100644
index 0000000..8257159
--- /dev/null
+++ b/grub-core/disk/lvm.c
@@ -0,0 +1,1091 @@
+/* lvm.c - module to read Logical Volumes. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008,2009,2011 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>
+#include <grub/partition.h>
+#include <grub/i18n.h>
+#include <grub/safemath.h>
+
+#ifdef GRUB_UTIL
+#include <grub/emu/misc.h>
+#include <grub/emu/hostdisk.h>
+#endif
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+struct cache_lv
+{
+ struct grub_diskfilter_lv *lv;
+ char *cache_pool;
+ char *origin;
+ struct cache_lv *next;
+};
+
+
+/* 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 grub_uint64_t
+grub_lvm_getvalue (const char ** const p, const char *str)
+{
+ *p = grub_strstr (*p, str);
+ if (! *p)
+ return 0;
+ *p += grub_strlen (str);
+ return grub_strtoull (*p, p, 10);
+}
+
+#if 0
+static int
+grub_lvm_checkvalue (char **p, char *str, char *tmpl)
+{
+ int tmpllen = grub_strlen (tmpl);
+ *p = grub_strstr (*p, str);
+ if (! *p)
+ return 0;
+ *p += grub_strlen (str);
+ if (**p != '"')
+ return 0;
+ return (grub_memcmp (*p + 1, tmpl, tmpllen) == 0 && (*p)[tmpllen + 1] == '"');
+}
+#endif
+
+static int
+grub_lvm_check_flag (const char *p, const char *str, const char *flag)
+{
+ grub_size_t len_str = grub_strlen (str), len_flag = grub_strlen (flag);
+ while (1)
+ {
+ const 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 void
+grub_lvm_free_cache_lvs (struct cache_lv *cache_lvs)
+{
+ struct cache_lv *cache;
+
+ while ((cache = cache_lvs))
+ {
+ cache_lvs = cache_lvs->next;
+
+ if (cache->lv)
+ {
+ unsigned int i;
+
+ for (i = 0; i < cache->lv->segment_count; ++i)
+ if (cache->lv->segments)
+ grub_free (cache->lv->segments[i].nodes);
+ grub_free (cache->lv->segments);
+ grub_free (cache->lv->fullname);
+ grub_free (cache->lv->idname);
+ grub_free (cache->lv->name);
+ }
+ grub_free (cache->lv);
+ grub_free (cache->origin);
+ grub_free (cache->cache_pool);
+ grub_free (cache);
+ }
+}
+
+static struct grub_diskfilter_vg *
+grub_lvm_detect (grub_disk_t disk,
+ struct grub_diskfilter_pv_id *id,
+ grub_disk_addr_t *start_sector)
+{
+ grub_err_t err;
+ grub_uint64_t mda_offset, mda_size;
+ grub_size_t ptr;
+ char buf[GRUB_LVM_LABEL_SIZE];
+ char vg_id[GRUB_LVM_ID_STRLEN+1];
+ char pv_id[GRUB_LVM_ID_STRLEN+1];
+ char *metadatabuf, *mda_end, *vgname;
+ const char *p, *q;
+ 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;
+ grub_size_t vgname_len;
+ struct grub_diskfilter_vg *vg;
+ struct grub_diskfilter_pv *pv;
+
+ /* Search for label. */
+ for (i = 0; i < GRUB_LVM_LABEL_SCAN_SECTORS; i++)
+ {
+ err = grub_disk_read (disk, i, 0, sizeof(buf), buf);
+ if (err)
+ goto fail;
+
+ if ((! grub_strncmp ((char *)lh->id, GRUB_LVM_LABEL_ID,
+ sizeof (lh->id)))
+ && (! grub_strncmp ((char *)lh->type, GRUB_LVM_LVM2_LABEL,
+ sizeof (lh->type))))
+ break;
+ }
+
+ /* Return if we didn't find a label. */
+ if (i == GRUB_LVM_LABEL_SCAN_SECTORS)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("no LVM signature found");
+#endif
+ goto fail;
+ }
+
+ /*
+ * We read a grub_lvm_pv_header and then 2 grub_lvm_disk_locns that
+ * immediately follow the PV header. Make sure we have space for both.
+ */
+ if (grub_le_to_cpu32 (lh->offset_xl) >=
+ GRUB_LVM_LABEL_SIZE - sizeof (struct grub_lvm_pv_header) -
+ 2 * sizeof (struct grub_lvm_disk_locn))
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("LVM PV header/disk locations are beyond the end of the block");
+#endif
+ goto fail;
+ }
+
+ pvh = (struct grub_lvm_pv_header *) (buf + grub_le_to_cpu32(lh->offset_xl));
+
+ for (i = 0, j = 0; i < GRUB_LVM_ID_LEN; i++)
+ {
+ pv_id[j++] = pvh->pv_uuid[i];
+ if ((i != 1) && (i != 29) && (i % 4 == 1))
+ pv_id[j++] = '-';
+ }
+ pv_id[j] = '\0';
+
+ dlocn = pvh->disk_areas_xl;
+
+ dlocn++;
+ /* Is it possible to have multiple data/metadata areas? I haven't
+ seen devices that have it. */
+ if (dlocn->offset)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "we don't support multiple LVM data areas");
+
+#ifdef GRUB_UTIL
+ grub_util_info ("we don't support multiple LVM data areas");
+#endif
+ goto fail;
+ }
+
+ dlocn++;
+ mda_offset = grub_le_to_cpu64 (dlocn->offset);
+ mda_size = grub_le_to_cpu64 (dlocn->size);
+
+ /* It's possible to have multiple copies of metadata areas, we just use the
+ first one. */
+
+ /* Allocate buffer space for the circular worst-case scenario. */
+ metadatabuf = grub_calloc (2, mda_size);
+ if (! metadatabuf)
+ goto fail;
+
+ err = grub_disk_read (disk, 0, mda_offset, mda_size, metadatabuf);
+ if (err)
+ goto fail2;
+
+ mdah = (struct grub_lvm_mda_header *) metadatabuf;
+ if ((grub_strncmp ((char *)mdah->magic, GRUB_LVM_FMTT_MAGIC,
+ sizeof (mdah->magic)))
+ || (grub_le_to_cpu32 (mdah->version) != GRUB_LVM_FMTT_VERSION))
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unknown LVM metadata header");
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown LVM metadata header");
+#endif
+ goto fail2;
+ }
+
+ rlocn = mdah->raw_locns;
+ if (grub_le_to_cpu64 (rlocn->offset) >= grub_le_to_cpu64 (mda_size))
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("metadata offset is beyond end of metadata area");
+#endif
+ goto fail2;
+ }
+
+ if (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) >
+ grub_le_to_cpu64 (mdah->size))
+ {
+ if (2 * mda_size < GRUB_LVM_MDA_HEADER_SIZE ||
+ (grub_le_to_cpu64 (rlocn->offset) + grub_le_to_cpu64 (rlocn->size) -
+ grub_le_to_cpu64 (mdah->size) > mda_size - GRUB_LVM_MDA_HEADER_SIZE))
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("cannot copy metadata wrap in circular buffer");
+#endif
+ goto fail2;
+ }
+
+ /* 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));
+ }
+
+ if (grub_add ((grub_size_t)metadatabuf,
+ (grub_size_t)grub_le_to_cpu64 (rlocn->offset),
+ &ptr))
+ {
+ error_parsing_metadata:
+#ifdef GRUB_UTIL
+ grub_util_info ("error parsing metadata");
+#endif
+ goto fail2;
+ }
+
+ p = q = (char *)ptr;
+
+ if (grub_add ((grub_size_t)metadatabuf, (grub_size_t)mda_size, &ptr))
+ goto error_parsing_metadata;
+
+ mda_end = (char *)ptr;
+
+ while (*q != ' ' && q < mda_end)
+ q++;
+
+ if (q == mda_end)
+ goto error_parsing_metadata;
+
+ vgname_len = q - p;
+ vgname = grub_malloc (vgname_len + 1);
+ if (!vgname)
+ goto fail2;
+
+ grub_memcpy (vgname, p, vgname_len);
+ vgname[vgname_len] = '\0';
+
+ p = grub_strstr (q, "id = \"");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("couldn't find ID");
+#endif
+ goto fail3;
+ }
+ p += sizeof ("id = \"") - 1;
+ grub_memcpy (vg_id, p, GRUB_LVM_ID_STRLEN);
+ vg_id[GRUB_LVM_ID_STRLEN] = '\0';
+
+ vg = grub_diskfilter_get_vg_by_uuid (GRUB_LVM_ID_STRLEN, vg_id);
+
+ if (! vg)
+ {
+ struct cache_lv *cache_lvs = NULL;
+
+ /* 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;
+ vg->uuid = grub_malloc (GRUB_LVM_ID_STRLEN);
+ if (! vg->uuid)
+ goto fail3;
+ grub_memcpy (vg->uuid, vg_id, GRUB_LVM_ID_STRLEN);
+ vg->uuid_len = GRUB_LVM_ID_STRLEN;
+
+ vg->extent_size = grub_lvm_getvalue (&p, "extent_size = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown extent size");
+#endif
+ goto fail4;
+ }
+
+ vg->lvs = NULL;
+ vg->pvs = NULL;
+
+ p = grub_strstr (p, "physical_volumes {");
+ if (p)
+ {
+ p += sizeof ("physical_volumes {") - 1;
+
+ /* Add all the pvs to the volume group. */
+ while (1)
+ {
+ grub_ssize_t s;
+ while (grub_isspace (*p) && p < mda_end)
+ p++;
+
+ if (p == mda_end)
+ goto fail4;
+
+ if (*p == '}')
+ break;
+
+ pv = grub_zalloc (sizeof (*pv));
+ q = p;
+ while (*q != ' ' && q < mda_end)
+ q++;
+
+ if (q == mda_end)
+ goto pvs_fail_noname;
+
+ 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;
+
+ pv->id.uuid = grub_malloc (GRUB_LVM_ID_STRLEN);
+ if (!pv->id.uuid)
+ goto pvs_fail;
+ grub_memcpy (pv->id.uuid, p, GRUB_LVM_ID_STRLEN);
+ pv->id.uuidlen = GRUB_LVM_ID_STRLEN;
+
+ pv->start_sector = grub_lvm_getvalue (&p, "pe_start = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown pe_start");
+#endif
+ goto pvs_fail;
+ }
+
+ p = grub_strchr (p, '}');
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("error parsing pe_start");
+#endif
+ goto pvs_fail;
+ }
+ p++;
+
+ pv->disk = NULL;
+ pv->next = vg->pvs;
+ vg->pvs = pv;
+
+ continue;
+ pvs_fail:
+ grub_free (pv->name);
+ pvs_fail_noname:
+ grub_free (pv);
+ goto fail4;
+ }
+ }
+ else
+ goto fail4;
+
+ p = grub_strstr (p, "logical_volumes {");
+ if (p)
+ {
+ p += sizeof ("logical_volumes {") - 1;
+
+ /* And add all the lvs to the volume group. */
+ while (1)
+ {
+ grub_ssize_t s;
+ int skip_lv = 0;
+ struct grub_diskfilter_lv *lv;
+ struct grub_diskfilter_segment *seg;
+ int is_pvmove;
+
+ while (grub_isspace (*p) && p < mda_end)
+ p++;
+
+ if (p == mda_end)
+ goto fail4;
+
+ if (*p == '}')
+ break;
+
+ lv = grub_zalloc (sizeof (*lv));
+
+ q = p;
+ while (*q != ' ' && q < mda_end)
+ q++;
+
+ if (q == mda_end)
+ goto lvs_fail;
+
+ s = q - p;
+ lv->name = grub_strndup (p, s);
+ if (!lv->name)
+ goto lvs_fail;
+
+ {
+ const char *iptr;
+ char *optr;
+
+ /*
+ * This is kind of hard to read with our safe (but rather
+ * baroque) math primatives, but it boils down to:
+ *
+ * sz0 = vgname_len * 2 + 1 +
+ * s * 2 + 1 +
+ * sizeof ("lvm/") - 1;
+ */
+ grub_size_t sz0 = vgname_len, sz1 = s;
+
+ if (grub_mul (sz0, 2, &sz0) ||
+ grub_add (sz0, 1, &sz0) ||
+ grub_mul (sz1, 2, &sz1) ||
+ grub_add (sz1, 1, &sz1) ||
+ grub_add (sz0, sz1, &sz0) ||
+ grub_add (sz0, sizeof ("lvm/") - 1, &sz0))
+ goto lvs_fail;
+
+ lv->fullname = grub_malloc (sz0);
+ if (!lv->fullname)
+ goto lvs_fail;
+
+ grub_memcpy (lv->fullname, "lvm/", sizeof ("lvm/") - 1);
+ optr = lv->fullname + sizeof ("lvm/") - 1;
+ for (iptr = vgname; iptr < vgname + vgname_len; iptr++)
+ {
+ *optr++ = *iptr;
+ if (*iptr == '-')
+ *optr++ = '-';
+ }
+ *optr++ = '-';
+ for (iptr = p; iptr < p + s; iptr++)
+ {
+ *optr++ = *iptr;
+ if (*iptr == '-')
+ *optr++ = '-';
+ }
+ *optr++ = 0;
+ lv->idname = grub_malloc (sizeof ("lvmid/")
+ + 2 * GRUB_LVM_ID_STRLEN + 1);
+ if (!lv->idname)
+ goto lvs_fail;
+ grub_memcpy (lv->idname, "lvmid/",
+ sizeof ("lvmid/") - 1);
+ grub_memcpy (lv->idname + sizeof ("lvmid/") - 1,
+ vg_id, GRUB_LVM_ID_STRLEN);
+ lv->idname[sizeof ("lvmid/") - 1 + GRUB_LVM_ID_STRLEN] = '/';
+
+ p = grub_strstr (q, "id = \"");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("couldn't find ID");
+#endif
+ goto lvs_fail;
+ }
+ p += sizeof ("id = \"") - 1;
+ grub_memcpy (lv->idname + sizeof ("lvmid/") - 1
+ + GRUB_LVM_ID_STRLEN + 1,
+ p, GRUB_LVM_ID_STRLEN);
+ lv->idname[sizeof ("lvmid/") - 1 + 2 * GRUB_LVM_ID_STRLEN + 1] = '\0';
+ }
+
+ lv->size = 0;
+
+ lv->visible = grub_lvm_check_flag (p, "status", "VISIBLE");
+ is_pvmove = grub_lvm_check_flag (p, "status", "PVMOVE");
+
+ lv->segment_count = grub_lvm_getvalue (&p, "segment_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown segment_count");
+#endif
+ goto lvs_fail;
+ }
+ lv->segments = grub_calloc (lv->segment_count, sizeof (*seg));
+ seg = lv->segments;
+
+ for (i = 0; i < lv->segment_count; i++)
+ {
+
+ p = grub_strstr (p, "segment");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown segment");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ seg->start_extent = grub_lvm_getvalue (&p, "start_extent = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown start_extent");
+#endif
+ goto lvs_segment_fail;
+ }
+ seg->extent_count = grub_lvm_getvalue (&p, "extent_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown extent_count");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ p = grub_strstr (p, "type = \"");
+ if (p == NULL)
+ goto lvs_segment_fail;
+ p += sizeof("type = \"") - 1;
+
+ lv->size += seg->extent_count * vg->extent_size;
+
+ if (grub_memcmp (p, "striped\"",
+ sizeof ("striped\"") - 1) == 0)
+ {
+ struct grub_diskfilter_node *stripe;
+
+ seg->type = GRUB_DISKFILTER_STRIPED;
+ seg->node_count = grub_lvm_getvalue (&p, "stripe_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripe_count");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ if (seg->node_count != 1)
+ {
+ seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripe_size");
+#endif
+ goto lvs_segment_fail;
+ }
+ }
+
+ seg->nodes = grub_calloc (seg->node_count,
+ sizeof (*stripe));
+ stripe = seg->nodes;
+
+ p = grub_strstr (p, "stripes = [");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripes");
+#endif
+ goto lvs_segment_fail2;
+ }
+ p += sizeof("stripes = [") - 1;
+
+ for (j = 0; j < seg->node_count; j++)
+ {
+ p = grub_strchr (p, '"');
+ if (p == NULL)
+ goto lvs_segment_fail2;
+ q = ++p;
+ while (q < mda_end && *q != '"')
+ q++;
+
+ if (q == mda_end)
+ goto lvs_segment_fail2;
+
+ s = q - p;
+
+ stripe->name = grub_malloc (s + 1);
+ if (stripe->name == NULL)
+ goto lvs_segment_fail2;
+
+ grub_memcpy (stripe->name, p, s);
+ stripe->name[s] = '\0';
+
+ p = q + 1;
+
+ stripe->start = grub_lvm_getvalue (&p, ",")
+ * vg->extent_size;
+ if (p == NULL)
+ {
+ grub_free (stripe->name);
+ goto lvs_segment_fail2;
+ }
+
+ stripe++;
+ }
+ }
+ else if (grub_memcmp (p, "mirror\"", sizeof ("mirror\"") - 1)
+ == 0)
+ {
+ seg->type = GRUB_DISKFILTER_MIRROR;
+ seg->node_count = grub_lvm_getvalue (&p, "mirror_count = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown mirror_count");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ seg->nodes = grub_zalloc (sizeof (seg->nodes[0])
+ * seg->node_count);
+
+ p = grub_strstr (p, "mirrors = [");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown mirrors");
+#endif
+ goto lvs_segment_fail2;
+ }
+ p += sizeof("mirrors = [") - 1;
+
+ for (j = 0; j < seg->node_count; j++)
+ {
+ char *lvname;
+
+ p = grub_strchr (p, '"');
+ if (p == NULL)
+ goto lvs_segment_fail2;
+ q = ++p;
+ while (q < mda_end && *q != '"')
+ q++;
+
+ if (q == mda_end)
+ goto lvs_segment_fail2;
+
+ s = q - p;
+
+ lvname = grub_malloc (s + 1);
+ if (lvname == NULL)
+ goto lvs_segment_fail2;
+
+ grub_memcpy (lvname, p, s);
+ lvname[s] = '\0';
+ seg->nodes[j].name = lvname;
+ p = q + 1;
+ }
+ /* Only first (original) is ok with in progress pvmove. */
+ if (is_pvmove)
+ seg->node_count = 1;
+ }
+ else if (grub_memcmp (p, "raid", sizeof ("raid") - 1) == 0
+ && ((p[sizeof ("raid") - 1] >= '4'
+ && p[sizeof ("raid") - 1] <= '6')
+ || p[sizeof ("raid") - 1] == '1')
+ && p[sizeof ("raidX") - 1] == '"')
+ {
+ switch (p[sizeof ("raid") - 1])
+ {
+ case '1':
+ seg->type = GRUB_DISKFILTER_MIRROR;
+ break;
+ case '4':
+ seg->type = GRUB_DISKFILTER_RAID4;
+ seg->layout = GRUB_RAID_LAYOUT_LEFT_ASYMMETRIC;
+ break;
+ case '5':
+ seg->type = GRUB_DISKFILTER_RAID5;
+ seg->layout = GRUB_RAID_LAYOUT_LEFT_SYMMETRIC;
+ break;
+ case '6':
+ seg->type = GRUB_DISKFILTER_RAID6;
+ seg->layout = (GRUB_RAID_LAYOUT_RIGHT_ASYMMETRIC
+ | GRUB_RAID_LAYOUT_MUL_FROM_POS);
+ break;
+ }
+ seg->node_count = grub_lvm_getvalue (&p, "device_count = ");
+
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown device_count");
+#endif
+ goto lvs_segment_fail;
+ }
+
+ if (seg->type != GRUB_DISKFILTER_MIRROR)
+ {
+ seg->stripe_size = grub_lvm_getvalue (&p, "stripe_size = ");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown stripe_size");
+#endif
+ goto lvs_segment_fail;
+ }
+ }
+
+ seg->nodes = grub_zalloc (sizeof (seg->nodes[0])
+ * seg->node_count);
+
+ p = grub_strstr (p, "raids = [");
+ if (p == NULL)
+ {
+#ifdef GRUB_UTIL
+ grub_util_info ("unknown raids");
+#endif
+ goto lvs_segment_fail2;
+ }
+ p += sizeof("raids = [") - 1;
+
+ for (j = 0; j < seg->node_count; j++)
+ {
+ char *lvname;
+
+ p = grub_strchr (p, '"');
+ p = p ? grub_strchr (p + 1, '"') : 0;
+ p = p ? grub_strchr (p + 1, '"') : 0;
+ if (p == NULL)
+ goto lvs_segment_fail2;
+ q = ++p;
+ while (*q != '"')
+ q++;
+
+ s = q - p;
+
+ lvname = grub_malloc (s + 1);
+ if (lvname == NULL)
+ goto lvs_segment_fail2;
+
+ grub_memcpy (lvname, p, s);
+ lvname[s] = '\0';
+ seg->nodes[j].name = lvname;
+ p = q + 1;
+ }
+ if (seg->type == GRUB_DISKFILTER_RAID4)
+ {
+ char *tmp;
+ tmp = seg->nodes[0].name;
+ grub_memmove (seg->nodes, seg->nodes + 1,
+ sizeof (seg->nodes[0])
+ * (seg->node_count - 1));
+ seg->nodes[seg->node_count - 1].name = tmp;
+ }
+ }
+ else if (grub_memcmp (p, "cache\"",
+ sizeof ("cache\"") - 1) == 0)
+ {
+ struct cache_lv *cache = NULL;
+
+ char *p2, *p3;
+ grub_size_t sz;
+
+ cache = grub_zalloc (sizeof (*cache));
+ if (!cache)
+ goto cache_lv_fail;
+ cache->lv = grub_zalloc (sizeof (*cache->lv));
+ if (!cache->lv)
+ goto cache_lv_fail;
+ grub_memcpy (cache->lv, lv, sizeof (*cache->lv));
+
+ if (lv->fullname)
+ {
+ cache->lv->fullname = grub_strdup (lv->fullname);
+ if (!cache->lv->fullname)
+ goto cache_lv_fail;
+ }
+ if (lv->idname)
+ {
+ cache->lv->idname = grub_strdup (lv->idname);
+ if (!cache->lv->idname)
+ goto cache_lv_fail;
+ }
+ if (lv->name)
+ {
+ cache->lv->name = grub_strdup (lv->name);
+ if (!cache->lv->name)
+ goto cache_lv_fail;
+ }
+
+ skip_lv = 1;
+
+ p2 = grub_strstr (p, "cache_pool = \"");
+ if (!p2)
+ goto cache_lv_fail;
+
+ p2 = grub_strchr (p2, '"');
+ if (!p2)
+ goto cache_lv_fail;
+
+ p3 = ++p2;
+ if (p3 == mda_end)
+ goto cache_lv_fail;
+ p3 = grub_strchr (p3, '"');
+ if (!p3)
+ goto cache_lv_fail;
+
+ sz = p3 - p2;
+
+ cache->cache_pool = grub_malloc (sz + 1);
+ if (!cache->cache_pool)
+ goto cache_lv_fail;
+ grub_memcpy (cache->cache_pool, p2, sz);
+ cache->cache_pool[sz] = '\0';
+
+ p2 = grub_strstr (p, "origin = \"");
+ if (!p2)
+ goto cache_lv_fail;
+
+ p2 = grub_strchr (p2, '"');
+ if (!p2)
+ goto cache_lv_fail;
+
+ p3 = ++p2;
+ if (p3 == mda_end)
+ goto cache_lv_fail;
+ p3 = grub_strchr (p3, '"');
+ if (!p3)
+ goto cache_lv_fail;
+
+ sz = p3 - p2;
+
+ cache->origin = grub_malloc (sz + 1);
+ if (!cache->origin)
+ goto cache_lv_fail;
+ grub_memcpy (cache->origin, p2, sz);
+ cache->origin[sz] = '\0';
+
+ cache->next = cache_lvs;
+ cache_lvs = cache;
+ break;
+
+ cache_lv_fail:
+ if (cache)
+ {
+ grub_free (cache->origin);
+ grub_free (cache->cache_pool);
+ if (cache->lv)
+ {
+ grub_free (cache->lv->fullname);
+ grub_free (cache->lv->idname);
+ grub_free (cache->lv->name);
+ }
+ grub_free (cache->lv);
+ grub_free (cache);
+ }
+ grub_lvm_free_cache_lvs (cache_lvs);
+ goto fail4;
+ }
+ else
+ {
+#ifdef GRUB_UTIL
+ char *p2;
+ p2 = grub_strchr (p, '"');
+ if (p2)
+ *p2 = 0;
+ grub_util_info ("unknown LVM type %s", p);
+ if (p2)
+ *p2 ='"';
+#endif
+ /* Found a non-supported type, give up and move on. */
+ skip_lv = 1;
+ break;
+ }
+
+ seg++;
+
+ continue;
+ lvs_segment_fail2:
+ grub_free (seg->nodes);
+ lvs_segment_fail:
+ goto fail4;
+ }
+
+ if (p != NULL)
+ p = grub_strchr (p, '}');
+ if (p == NULL)
+ goto lvs_fail;
+ p += 3;
+
+ if (skip_lv)
+ {
+ grub_free (lv->name);
+ grub_free (lv);
+ continue;
+ }
+
+ lv->vg = vg;
+ lv->next = vg->lvs;
+ vg->lvs = lv;
+
+ continue;
+ lvs_fail:
+ grub_free (lv->name);
+ grub_free (lv);
+ goto fail4;
+ }
+ }
+
+ /* Match lvs. */
+ {
+ struct grub_diskfilter_lv *lv1;
+ struct grub_diskfilter_lv *lv2;
+ for (lv1 = vg->lvs; lv1; lv1 = lv1->next)
+ for (i = 0; i < lv1->segment_count; i++)
+ for (j = 0; j < lv1->segments[i].node_count; j++)
+ {
+ if (vg->pvs)
+ for (pv = vg->pvs; pv; pv = pv->next)
+ {
+ if (! grub_strcmp (pv->name,
+ lv1->segments[i].nodes[j].name))
+ {
+ lv1->segments[i].nodes[j].pv = pv;
+ break;
+ }
+ }
+ if (lv1->segments[i].nodes[j].pv == NULL)
+ for (lv2 = vg->lvs; lv2; lv2 = lv2->next)
+ {
+ if (lv1 == lv2)
+ continue;
+ if (grub_strcmp (lv2->name,
+ lv1->segments[i].nodes[j].name) == 0)
+ lv1->segments[i].nodes[j].lv = lv2;
+ }
+ }
+
+ }
+
+ {
+ struct cache_lv *cache;
+
+ for (cache = cache_lvs; cache; cache = cache->next)
+ {
+ struct grub_diskfilter_lv *lv;
+
+ for (lv = vg->lvs; lv; lv = lv->next)
+ if (grub_strcmp (lv->name, cache->origin) == 0)
+ break;
+ if (lv)
+ {
+ cache->lv->segments = grub_calloc (lv->segment_count, sizeof (*lv->segments));
+ if (!cache->lv->segments)
+ {
+ grub_lvm_free_cache_lvs (cache_lvs);
+ goto fail4;
+ }
+ grub_memcpy (cache->lv->segments, lv->segments, lv->segment_count * sizeof (*lv->segments));
+
+ for (i = 0; i < lv->segment_count; ++i)
+ {
+ struct grub_diskfilter_node *nodes = lv->segments[i].nodes;
+ grub_size_t node_count = lv->segments[i].node_count;
+
+ cache->lv->segments[i].nodes = grub_calloc (node_count, sizeof (*nodes));
+ if (!cache->lv->segments[i].nodes)
+ {
+ for (j = 0; j < i; ++j)
+ grub_free (cache->lv->segments[j].nodes);
+ grub_free (cache->lv->segments);
+ cache->lv->segments = NULL;
+ grub_lvm_free_cache_lvs (cache_lvs);
+ goto fail4;
+ }
+ grub_memcpy (cache->lv->segments[i].nodes, nodes, node_count * sizeof (*nodes));
+ }
+
+ if (cache->lv->segments)
+ {
+ cache->lv->segment_count = lv->segment_count;
+ cache->lv->vg = vg;
+ cache->lv->next = vg->lvs;
+ vg->lvs = cache->lv;
+ cache->lv = NULL;
+ }
+ }
+ }
+ }
+
+ grub_lvm_free_cache_lvs (cache_lvs);
+ if (grub_diskfilter_vg_register (vg))
+ goto fail4;
+ }
+ else
+ {
+ grub_free (vgname);
+ }
+
+ id->uuid = grub_malloc (GRUB_LVM_ID_STRLEN);
+ if (!id->uuid)
+ goto fail4;
+ grub_memcpy (id->uuid, pv_id, GRUB_LVM_ID_STRLEN);
+ id->uuidlen = GRUB_LVM_ID_STRLEN;
+ grub_free (metadatabuf);
+ *start_sector = -1;
+ return vg;
+
+ /* Failure path. */
+ fail4:
+ grub_free (vg);
+ fail3:
+ grub_free (vgname);
+
+ fail2:
+ grub_free (metadatabuf);
+ fail:
+ return NULL;
+}
+
+
+
+static struct grub_diskfilter grub_lvm_dev = {
+ .name = "lvm",
+ .detect = grub_lvm_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT (lvm)
+{
+ grub_diskfilter_register_back (&grub_lvm_dev);
+}
+
+GRUB_MOD_FINI (lvm)
+{
+ grub_diskfilter_unregister (&grub_lvm_dev);
+}
diff --git a/grub-core/disk/mdraid1x_linux.c b/grub-core/disk/mdraid1x_linux.c
new file mode 100644
index 0000000..38444b0
--- /dev/null
+++ b/grub-core/disk/mdraid1x_linux.c
@@ -0,0 +1,233 @@
+/* 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/diskfilter.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* Linux RAID on disk structures and constants,
+ copied from include/linux/raid/md_p.h. */
+
+#define SB_MAGIC 0xa92b4efc
+
+/*
+ * The version-1 superblock :
+ * All numeric fields are little-endian.
+ *
+ * Total size: 256 bytes plus 2 per device.
+ * 1K allows 384 devices.
+ */
+
+struct grub_raid_super_1x
+{
+ /* Constant array information - 128 bytes. */
+ grub_uint32_t magic; /* MD_SB_MAGIC: 0xa92b4efc - little endian. */
+ grub_uint32_t major_version; /* 1. */
+ grub_uint32_t feature_map; /* Bit 0 set if 'bitmap_offset' is meaningful. */
+ grub_uint32_t pad0; /* Always set to 0 when writing. */
+
+ grub_uint8_t set_uuid[16]; /* User-space generated. */
+ char set_name[32]; /* Set and interpreted by user-space. */
+
+ grub_uint64_t ctime; /* Lo 40 bits are seconds, top 24 are microseconds or 0. */
+ grub_uint32_t level; /* -4 (multipath), -1 (linear), 0,1,4,5. */
+ grub_uint32_t layout; /* only for raid5 and raid10 currently. */
+ grub_uint64_t size; /* Used size of component devices, in 512byte sectors. */
+
+ grub_uint32_t chunksize; /* In 512byte sectors. */
+ grub_uint32_t raid_disks;
+ grub_uint32_t bitmap_offset; /* Sectors after start of superblock that bitmap starts
+ * NOTE: signed, so bitmap can be before superblock
+ * only meaningful of feature_map[0] is set.
+ */
+
+ /* These are only valid with feature bit '4'. */
+ grub_uint32_t new_level; /* New level we are reshaping to. */
+ grub_uint64_t reshape_position; /* Next address in array-space for reshape. */
+ grub_uint32_t delta_disks; /* Change in number of raid_disks. */
+ grub_uint32_t new_layout; /* New layout. */
+ grub_uint32_t new_chunk; /* New chunk size (512byte sectors). */
+ grub_uint8_t pad1[128 - 124]; /* Set to 0 when written. */
+
+ /* Constant this-device information - 64 bytes. */
+ grub_uint64_t data_offset; /* Sector start of data, often 0. */
+ grub_uint64_t data_size; /* Sectors in this device that can be used for data. */
+ grub_uint64_t super_offset; /* Sector start of this superblock. */
+ grub_uint64_t recovery_offset; /* Sectors before this offset (from data_offset) have been recovered. */
+ grub_uint32_t dev_number; /* Permanent identifier of this device - not role in raid. */
+ grub_uint32_t cnt_corrected_read; /* Number of read errors that were corrected by re-writing. */
+ grub_uint8_t device_uuid[16]; /* User-space setable, ignored by kernel. */
+ grub_uint8_t devflags; /* Per-device flags. Only one defined... */
+ grub_uint8_t pad2[64 - 57]; /* Set to 0 when writing. */
+
+ /* Array state information - 64 bytes. */
+ grub_uint64_t utime; /* 40 bits second, 24 btes microseconds. */
+ grub_uint64_t events; /* Incremented when superblock updated. */
+ grub_uint64_t resync_offset; /* Data before this offset (from data_offset) known to be in sync. */
+ grub_uint32_t sb_csum; /* Checksum upto devs[max_dev]. */
+ grub_uint32_t max_dev; /* Size of devs[] array to consider. */
+ grub_uint8_t pad3[64 - 32]; /* Set to 0 when writing. */
+
+ /* Device state information. Indexed by dev_number.
+ * 2 bytes per device.
+ * Note there are no per-device state flags. State information is rolled
+ * into the 'roles' value. If a device is spare or faulty, then it doesn't
+ * have a meaningful role.
+ */
+ grub_uint16_t dev_roles[0]; /* Role in array, or 0xffff for a spare, or 0xfffe for faulty. */
+};
+/* Could be GRUB_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 struct grub_diskfilter_vg *
+grub_mdraid_detect (grub_disk_t disk,
+ struct grub_diskfilter_pv_id *id,
+ grub_disk_addr_t *start_sector)
+{
+ grub_uint64_t size;
+ grub_uint8_t minor_version;
+
+ size = grub_disk_native_sectors (disk);
+
+ /* Check for an 1.x superblock.
+ * It's always aligned to a 4K boundary
+ * and depending on the minor version it can be:
+ * 0: At least 8K, but less than 12K, from end of device
+ * 1: At start of device
+ * 2: 4K from start of device.
+ */
+
+ for (minor_version = 0; minor_version < 3; ++minor_version)
+ {
+ grub_disk_addr_t sector = 0;
+ struct grub_raid_super_1x sb;
+ grub_uint16_t role;
+ grub_uint32_t level;
+ struct grub_diskfilter_vg *array;
+ char *uuid;
+
+ if (size == GRUB_DISK_SIZE_UNKNOWN && minor_version == 0)
+ continue;
+
+ switch (minor_version)
+ {
+ case 0:
+ sector = (size - 8 * 2) & ~(4 * 2 - 1);
+ break;
+ case 1:
+ sector = 0;
+ break;
+ case 2:
+ sector = 4 * 2;
+ break;
+ }
+
+ if (grub_disk_read (disk, sector, 0, sizeof (struct grub_raid_super_1x),
+ &sb))
+ return NULL;
+
+ if (sb.magic != grub_cpu_to_le32_compile_time (SB_MAGIC)
+ || grub_le_to_cpu64 (sb.super_offset) != sector)
+ continue;
+
+ if (sb.major_version != grub_cpu_to_le32_compile_time (1))
+ /* Unsupported version. */
+ return NULL;
+
+ level = grub_le_to_cpu32 (sb.level);
+
+ /* Multipath. */
+ if ((int) level == -4)
+ level = 1;
+
+ if (level != 0 && level != 1 && level != 4 &&
+ level != 5 && level != 6 && level != 10)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "Unsupported RAID level: %d", sb.level);
+ return NULL;
+ }
+
+ if (grub_le_to_cpu32 (sb.dev_number) >=
+ grub_le_to_cpu32 (sb.max_dev))
+ /* Spares aren't implemented. */
+ return NULL;
+
+ if (grub_disk_read (disk, sector,
+ (char *) (sb.dev_roles + grub_le_to_cpu32 (sb.dev_number))
+ - (char *) &sb,
+ sizeof (role), &role))
+ return NULL;
+
+ if (grub_le_to_cpu16 (role)
+ >= grub_le_to_cpu32 (sb.raid_disks))
+ /* Spares aren't implemented. */
+ return NULL;
+
+ id->uuidlen = 0;
+ id->id = grub_le_to_cpu16 (role);
+
+ uuid = grub_malloc (16);
+ if (!uuid)
+ return NULL;
+
+ grub_memcpy (uuid, sb.set_uuid, 16);
+
+ *start_sector = grub_le_to_cpu64 (sb.data_offset);
+
+ array = grub_diskfilter_make_raid (16, uuid,
+ grub_le_to_cpu32 (sb.raid_disks),
+ sb.set_name,
+ (sb.size)
+ ? grub_le_to_cpu64 (sb.size)
+ : grub_le_to_cpu64 (sb.data_size),
+ grub_le_to_cpu32 (sb.chunksize),
+ grub_le_to_cpu32 (sb.layout),
+ grub_le_to_cpu32 (sb.level));
+
+ return array;
+ }
+
+ /* not 1.x raid. */
+ return NULL;
+}
+
+static struct grub_diskfilter grub_mdraid_dev = {
+ .name = "mdraid1x",
+ .detect = grub_mdraid_detect,
+ .next = 0
+};
+
+GRUB_MOD_INIT (mdraid1x)
+{
+ grub_diskfilter_register_front (&grub_mdraid_dev);
+}
+
+GRUB_MOD_FINI (mdraid1x)
+{
+ grub_diskfilter_unregister (&grub_mdraid_dev);
+}
diff --git a/grub-core/disk/mdraid_linux.c b/grub-core/disk/mdraid_linux.c
new file mode 100644
index 0000000..e40216f
--- /dev/null
+++ b/grub-core/disk/mdraid_linux.c
@@ -0,0 +1,298 @@
+/* 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/diskfilter.h>
+
+/* Linux RAID on disk structures and constants,
+ copied from include/linux/raid/md_p.h. */
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#ifdef MODE_BIGENDIAN
+#define grub_md_to_cpu64 grub_be_to_cpu64
+#define grub_md_to_cpu32 grub_be_to_cpu32
+#define grub_md_to_cpu16 grub_be_to_cpu16
+#define grub_cpu_to_md64_compile_time grub_cpu_to_be64_compile_time
+#define grub_cpu_to_md32_compile_time grub_cpu_to_be32_compile_time
+#define grub_cpu_to_md16_compile_time grub_cpu_to_be16_compile_time
+#else
+#define grub_md_to_cpu64 grub_le_to_cpu64
+#define grub_md_to_cpu32 grub_le_to_cpu32
+#define grub_md_to_cpu16 grub_le_to_cpu16
+#define grub_cpu_to_md64_compile_time grub_cpu_to_le64_compile_time
+#define grub_cpu_to_md32_compile_time grub_cpu_to_le32_compile_time
+#define grub_cpu_to_md16_compile_time grub_cpu_to_le16_compile_time
+#endif
+
+#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;
+} GRUB_PACKED;
+
+static struct grub_diskfilter_vg *
+grub_mdraid_detect (grub_disk_t disk,
+ struct grub_diskfilter_pv_id *id,
+ grub_disk_addr_t *start_sector)
+{
+ grub_disk_addr_t sector;
+ grub_uint64_t size;
+ struct grub_raid_super_09 *sb = NULL;
+ grub_uint32_t *uuid;
+ grub_uint32_t level;
+ struct grub_diskfilter_vg *ret;
+
+ /* The sector where the mdraid 0.90 superblock is stored, if available. */
+ size = grub_disk_native_sectors (disk);
+ if (size == GRUB_DISK_SIZE_UNKNOWN)
+ /* not 0.9x raid. */
+ return NULL;
+ sector = NEW_SIZE_SECTORS (size);
+
+ sb = grub_malloc (sizeof (*sb));
+ if (!sb)
+ return NULL;
+
+ if (grub_disk_read (disk, sector, 0, SB_BYTES, sb))
+ goto fail;
+
+ /* Look whether there is a mdraid 0.90 superblock. */
+ if (sb->md_magic != grub_cpu_to_md32_compile_time (SB_MAGIC))
+ /* not 0.9x raid. */
+ goto fail;
+
+ if (sb->major_version != grub_cpu_to_md32_compile_time (0)
+ || sb->minor_version != grub_cpu_to_md32_compile_time (90))
+ /* Unsupported version. */
+ goto fail;
+
+ /* No need for explicit check that sb->size is 0 (unspecified) since
+ 0 >= non-0 is false. */
+ if (((grub_disk_addr_t) grub_md_to_cpu32 (sb->size)) * 2 >= size)
+ goto fail;
+
+ /* FIXME: Check the checksum. */
+
+ level = grub_md_to_cpu32 (sb->level);
+ /* Multipath. */
+ if ((int) level == -4)
+ level = 1;
+
+ if (level != 0 && level != 1 && level != 4 &&
+ level != 5 && level != 6 && level != 10)
+ {
+ grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "unsupported RAID level: %d", level);
+ goto fail;
+ }
+ if (grub_md_to_cpu32 (sb->this_disk.number) == 0xffff
+ || grub_md_to_cpu32 (sb->this_disk.number) == 0xfffe)
+ /* Spares aren't implemented. */
+ goto fail;
+
+ uuid = grub_malloc (16);
+ if (!uuid)
+ goto fail;
+
+ uuid[0] = grub_swap_bytes32 (sb->set_uuid0);
+ uuid[1] = grub_swap_bytes32 (sb->set_uuid1);
+ uuid[2] = grub_swap_bytes32 (sb->set_uuid2);
+ uuid[3] = grub_swap_bytes32 (sb->set_uuid3);
+
+ *start_sector = 0;
+
+ id->uuidlen = 0;
+ id->id = grub_md_to_cpu32 (sb->this_disk.number);
+
+ char buf[32];
+ grub_snprintf (buf, sizeof (buf), "md%d", grub_md_to_cpu32 (sb->md_minor));
+ ret = grub_diskfilter_make_raid (16, (char *) uuid,
+ grub_md_to_cpu32 (sb->raid_disks), buf,
+ (sb->size) ? ((grub_disk_addr_t)
+ grub_md_to_cpu32 (sb->size)) * 2
+ : sector,
+ grub_md_to_cpu32 (sb->chunk_size) >> 9,
+ grub_md_to_cpu32 (sb->layout),
+ level);
+ grub_free (sb);
+ return ret;
+
+ fail:
+ grub_free (sb);
+ return NULL;
+}
+
+static struct grub_diskfilter grub_mdraid_dev = {
+#ifdef MODE_BIGENDIAN
+ .name = "mdraid09_be",
+#else
+ .name = "mdraid09",
+#endif
+ .detect = grub_mdraid_detect,
+ .next = 0
+};
+
+#ifdef MODE_BIGENDIAN
+GRUB_MOD_INIT (mdraid09_be)
+#else
+GRUB_MOD_INIT (mdraid09)
+#endif
+{
+ grub_diskfilter_register_front (&grub_mdraid_dev);
+}
+
+#ifdef MODE_BIGENDIAN
+GRUB_MOD_FINI (mdraid09_be)
+#else
+GRUB_MOD_FINI (mdraid09)
+#endif
+{
+ grub_diskfilter_unregister (&grub_mdraid_dev);
+}
diff --git a/grub-core/disk/mdraid_linux_be.c b/grub-core/disk/mdraid_linux_be.c
new file mode 100644
index 0000000..539bcf4
--- /dev/null
+++ b/grub-core/disk/mdraid_linux_be.c
@@ -0,0 +1,2 @@
+#define MODE_BIGENDIAN 1
+#include "mdraid_linux.c"
diff --git a/grub-core/disk/memdisk.c b/grub-core/disk/memdisk.c
new file mode 100644
index 0000000..613779c
--- /dev/null
+++ b/grub-core/disk/memdisk.c
@@ -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>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static char *memdisk_addr;
+static grub_off_t memdisk_size = 0;
+
+static int
+grub_memdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ return hook ("memdisk", hook_data);
+}
+
+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->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE;
+ disk->id = 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,
+ .disk_iterate = grub_memdisk_iterate,
+ .disk_open = grub_memdisk_open,
+ .disk_close = grub_memdisk_close,
+ .disk_read = grub_memdisk_read,
+ .disk_write = grub_memdisk_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(memdisk)
+{
+ struct grub_module_header *header;
+ FOR_MODULES (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);
+ break;
+ }
+}
+
+GRUB_MOD_FINI(memdisk)
+{
+ if (! memdisk_size)
+ return;
+ grub_free (memdisk_addr);
+ grub_disk_dev_unregister (&grub_memdisk_dev);
+}
diff --git a/grub-core/disk/pata.c b/grub-core/disk/pata.c
new file mode 100644
index 0000000..c757e65
--- /dev/null
+++ b/grub-core/disk/pata.c
@@ -0,0 +1,556 @@
+/* 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/scsi.h>
+#include <grub/disk.h>
+#include <grub/dl.h>
+#include <grub/mm.h>
+#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
+#include <grub/pci.h>
+#include <grub/cs5536.h>
+#else
+#define GRUB_MACHINE_PCI_IO_BASE 0xb4000000
+#endif
+#include <grub/time.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* At the moment, only two IDE ports are supported. */
+static const grub_port_t grub_pata_ioaddress[] = { GRUB_ATA_CH0_PORT1,
+ GRUB_ATA_CH1_PORT1 };
+
+struct grub_pata_device
+{
+ /* IDE port to use. */
+ int port;
+
+ /* IO addresses on which the registers for this device can be
+ found. */
+ grub_port_t ioaddress;
+
+ /* Two devices can be connected to a single cable. Use this field
+ to select device 0 (commonly known as "master") or device 1
+ (commonly known as "slave"). */
+ int device;
+
+ int present;
+
+ struct grub_pata_device *next;
+};
+
+static struct grub_pata_device *grub_pata_devices;
+
+static inline void
+grub_pata_regset (struct grub_pata_device *dev, int reg, int val)
+{
+ grub_outb (val, dev->ioaddress + reg);
+}
+
+static inline grub_uint8_t
+grub_pata_regget (struct grub_pata_device *dev, int reg)
+{
+ return grub_inb (dev->ioaddress + reg);
+}
+
+/* Wait for !BSY. */
+static grub_err_t
+grub_pata_wait_not_busy (struct grub_pata_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_pata_regget (dev, GRUB_ATA_REG_STATUS))
+ & GRUB_ATA_STATUS_BUSY)
+ {
+ if (i >= milliseconds)
+ {
+ grub_dprintf ("pata", "timeout: %dms, status=0x%x\n",
+ milliseconds, sts);
+ return grub_error (GRUB_ERR_TIMEOUT, "PATA timeout");
+ }
+
+ grub_millisleep (1);
+ i++;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+static inline grub_err_t
+grub_pata_check_ready (struct grub_pata_device *dev, int spinup)
+{
+ if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
+ return grub_pata_wait_not_busy (dev, spinup ? GRUB_ATA_TOUT_SPINUP
+ : GRUB_ATA_TOUT_STD);
+
+ return GRUB_ERR_NONE;
+}
+
+static inline void
+grub_pata_wait (void)
+{
+ grub_millisleep (50);
+}
+
+#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
+#define grub_ata_to_cpu16(x) ((grub_uint16_t) (x))
+#define grub_cpu_to_ata16(x) ((grub_uint16_t) (x))
+#else
+#define grub_ata_to_cpu16 grub_le_to_cpu16
+#define grub_cpu_to_ata16 grub_cpu_to_le16
+#endif
+
+static void
+grub_pata_pio_read (struct grub_pata_device *dev, char *buf, grub_size_t size)
+{
+ unsigned int i;
+
+ /* Read in the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ grub_set_unaligned16 (buf + 2 * i,
+ grub_ata_to_cpu16 (grub_inw(dev->ioaddress
+ + GRUB_ATA_REG_DATA)));
+ if (size & 1)
+ buf[size - 1] = (char) grub_ata_to_cpu16 (grub_inw (dev->ioaddress
+ + GRUB_ATA_REG_DATA));
+}
+
+static void
+grub_pata_pio_write (struct grub_pata_device *dev, char *buf, grub_size_t size)
+{
+ unsigned int i;
+
+ /* Write the data, word by word. */
+ for (i = 0; i < size / 2; i++)
+ grub_outw(grub_cpu_to_ata16 (grub_get_unaligned16 (buf + 2 * i)), dev->ioaddress + GRUB_ATA_REG_DATA);
+}
+
+/* ATA pass through support, used by hdparm.mod. */
+static grub_err_t
+grub_pata_readwrite (struct grub_ata *disk,
+ struct grub_disk_ata_pass_through_parms *parms,
+ int spinup)
+{
+ struct grub_pata_device *dev = (struct grub_pata_device *) disk->data;
+ grub_size_t nread = 0;
+ int i;
+
+ if (! (parms->cmdsize == 0 || parms->cmdsize == 12))
+ return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
+ "ATAPI non-12 byte commands not supported");
+
+ grub_dprintf ("pata", "pata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
+ parms->taskfile.cmd,
+ parms->taskfile.features,
+ parms->taskfile.sectors);
+ grub_dprintf ("pata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%"
+ PRIuGRUB_SIZE "\n",
+ parms->taskfile.lba_high,
+ parms->taskfile.lba_mid,
+ parms->taskfile.lba_low, parms->size);
+
+ /* Set registers. */
+ grub_pata_regset (dev, GRUB_ATA_REG_DISK, (dev->device << 4)
+ | (parms->taskfile.disk & 0xef));
+ if (grub_pata_check_ready (dev, spinup))
+ return grub_errno;
+
+ for (i = GRUB_ATA_REG_SECTORS; i <= GRUB_ATA_REG_LBAHIGH; i++)
+ grub_pata_regset (dev, i,
+ parms->taskfile.raw[7 + (i - GRUB_ATA_REG_SECTORS)]);
+ for (i = GRUB_ATA_REG_FEATURES; i <= GRUB_ATA_REG_LBAHIGH; i++)
+ grub_pata_regset (dev, i, parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES]);
+
+ /* Start command. */
+ grub_pata_regset (dev, GRUB_ATA_REG_CMD, parms->taskfile.cmd);
+
+ /* Wait for !BSY. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ /* Check status. */
+ grub_int8_t sts = grub_pata_regget (dev, GRUB_ATA_REG_STATUS);
+ grub_dprintf ("pata", "status=0x%x\n", sts);
+
+ if (parms->cmdsize)
+ {
+ grub_uint8_t irs;
+ /* Wait for !BSY. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ irs = grub_pata_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) == GRUB_ATAPI_IREASON_CMD_OUT))
+ return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
+ /* Write the packet. */
+ grub_pata_pio_write (dev, parms->cmd, parms->cmdsize);
+ }
+
+ /* Transfer data. */
+ while (nread < parms->size
+ && (sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ == GRUB_ATA_STATUS_DRQ)
+ {
+ unsigned cnt;
+
+ /* Wait for !BSY. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ if (parms->cmdsize)
+ {
+ if ((grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON)
+ & GRUB_ATAPI_IREASON_MASK) != GRUB_ATAPI_IREASON_DATA_IN)
+ return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
+
+ cnt = grub_pata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
+ | grub_pata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
+ grub_dprintf("pata", "DRQ count=%u\n", cnt);
+
+ /* Count of last transfer may be uneven. */
+ if (! (0 < cnt && cnt <= parms->size - nread
+ && (! (cnt & 1) || cnt == parms->size - nread)))
+ return grub_error (GRUB_ERR_READ_ERROR,
+ "invalid ATAPI transfer count");
+ }
+ else
+ cnt = GRUB_DISK_SECTOR_SIZE;
+ if (cnt > parms->size - nread)
+ cnt = parms->size - nread;
+
+ if (parms->write)
+ grub_pata_pio_write (dev, (char *) parms->buffer + nread, cnt);
+ else
+ grub_pata_pio_read (dev, (char *) parms->buffer + nread, cnt);
+
+ nread += cnt;
+ }
+ if (parms->write)
+ {
+ /* Check for write error. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS)
+ & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
+ }
+ parms->size = nread;
+
+ /* Wait for !BSY. */
+ if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
+ return grub_errno;
+
+ /* Return registers. */
+ for (i = GRUB_ATA_REG_ERROR; i <= GRUB_ATA_REG_STATUS; i++)
+ parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES] = grub_pata_regget (dev, i);
+
+ grub_dprintf ("pata", "status=0x%x, error=0x%x, sectors=0x%x\n",
+ parms->taskfile.status,
+ parms->taskfile.error,
+ parms->taskfile.sectors);
+
+ if (parms->taskfile.status
+ & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
+ return grub_error (GRUB_ERR_READ_ERROR, "PATA passthrough failed");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+check_device (struct grub_pata_device *dev)
+{
+ grub_pata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
+ grub_pata_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_pata_regset (dev, GRUB_ATA_REG_SECTORS, 0x5A);
+ grub_pata_wait ();
+ grub_uint8_t sec = grub_pata_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. */
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_pata_device_initialize (int port, int device, int addr)
+{
+ struct grub_pata_device *dev;
+ struct grub_pata_device **devp;
+ grub_err_t err;
+
+ grub_dprintf ("pata", "detecting device %d,%d (0x%x)\n",
+ port, device, addr);
+
+ 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->present = 1;
+ dev->next = NULL;
+
+ /* Register the device. */
+ for (devp = &grub_pata_devices; *devp; devp = &(*devp)->next);
+ *devp = dev;
+
+ err = check_device (dev);
+ if (err == GRUB_ERR_UNKNOWN_DEVICE)
+ {
+ grub_dprintf ("pata", "%s\n", grub_errmsg);
+ grub_error_pop ();
+ }
+
+ if (err)
+ grub_print_error ();
+
+ return 0;
+}
+
+#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
+static int
+grub_pata_pciinit (grub_pci_device_t dev,
+ grub_pci_id_t pciid,
+ void *data __attribute__ ((unused)))
+{
+ 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 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;
+
+ /* If the channel is in compatibility mode, just assign the
+ default registers. */
+ if (compat == 0 && !compat_use[i])
+ {
+ rega = grub_pata_ioaddress[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) && (bar1 & ~3))
+ {
+ rega = bar1 & ~3;
+ addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+ grub_pci_write_word (addr, grub_pci_read_word (addr)
+ | GRUB_PCI_COMMAND_IO_ENABLED
+ | GRUB_PCI_COMMAND_MEM_ENABLED
+ | GRUB_PCI_COMMAND_BUS_MASTER);
+
+ }
+ }
+
+ grub_dprintf ("pata",
+ "PCI dev (%d,%d,%d) compat=%d rega=0x%x\n",
+ grub_pci_get_bus (dev), grub_pci_get_device (dev),
+ grub_pci_get_function (dev), compat, rega);
+
+ if (rega)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ grub_pata_device_initialize (controller * 2 + i, 0, rega);
+
+ /* 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_pata_device_initialize (controller * 2 + i, 1, rega);
+
+ /* Likewise. */
+ if (grub_errno)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+ }
+
+ controller++;
+
+ return 0;
+}
+
+static grub_err_t
+grub_pata_initialize (void)
+{
+ grub_pci_iterate (grub_pata_pciinit, NULL);
+ return 0;
+}
+#else
+static grub_err_t
+grub_pata_initialize (void)
+{
+ int i;
+ for (i = 0; i < 2; i++)
+ {
+ grub_pata_device_initialize (i, 0, grub_pata_ioaddress[i]);
+ grub_pata_device_initialize (i, 1, grub_pata_ioaddress[i]);
+ }
+ return 0;
+}
+#endif
+
+static grub_err_t
+grub_pata_open (int id, int devnum, struct grub_ata *ata)
+{
+ struct grub_pata_device *dev;
+ struct grub_pata_device *devfnd = 0;
+ grub_err_t err;
+
+ if (id != GRUB_SCSI_SUBSYSTEM_PATA)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a PATA device");
+
+ for (dev = grub_pata_devices; dev; dev = dev->next)
+ {
+ if (dev->port * 2 + dev->device == devnum)
+ {
+ devfnd = dev;
+ break;
+ }
+ }
+
+ grub_dprintf ("pata", "opening PATA dev `ata%d'\n", devnum);
+
+ if (! devfnd)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such PATA device");
+
+ err = check_device (devfnd);
+ if (err)
+ return err;
+
+ ata->data = devfnd;
+ ata->dma = 0;
+ ata->maxbuffer = 256 * 512;
+ ata->present = &devfnd->present;
+
+ return GRUB_ERR_NONE;
+}
+
+static int
+grub_pata_iterate (grub_ata_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_pata_device *dev;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ for (dev = grub_pata_devices; dev; dev = dev->next)
+ if (hook (GRUB_SCSI_SUBSYSTEM_PATA, dev->port * 2 + dev->device,
+ hook_data))
+ return 1;
+
+ return 0;
+}
+
+
+static struct grub_ata_dev grub_pata_dev =
+ {
+ .iterate = grub_pata_iterate,
+ .open = grub_pata_open,
+ .readwrite = grub_pata_readwrite,
+ };
+
+
+
+
+GRUB_MOD_INIT(ata_pthru)
+{
+ grub_stop_disk_firmware ();
+
+ /* ATA initialization. */
+ grub_pata_initialize ();
+
+ grub_ata_dev_register (&grub_pata_dev);
+}
+
+GRUB_MOD_FINI(ata_pthru)
+{
+ grub_ata_dev_unregister (&grub_pata_dev);
+}
diff --git a/grub-core/disk/raid5_recover.c b/grub-core/disk/raid5_recover.c
new file mode 100644
index 0000000..4ace917
--- /dev/null
+++ b/grub-core/disk/raid5_recover.c
@@ -0,0 +1,76 @@
+/* raid5_recover.c - module to recover from faulty RAID4/5 arrays. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/misc.h>
+#include <grub/diskfilter.h>
+#include <grub/crypto.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+static grub_err_t
+grub_raid5_recover (struct grub_diskfilter_segment *array, int disknr,
+ char *buf, grub_disk_addr_t sector, grub_size_t 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->node_count; i++)
+ {
+ grub_err_t err;
+
+ if (i == disknr)
+ continue;
+
+ err = grub_diskfilter_read_node (&array->nodes[i], sector,
+ size >> GRUB_DISK_SECTOR_BITS, buf2);
+
+ if (err)
+ {
+ grub_free (buf2);
+ return err;
+ }
+
+ grub_crypto_xor (buf, buf, buf2, size);
+ }
+
+ grub_free (buf2);
+
+ return GRUB_ERR_NONE;
+}
+
+GRUB_MOD_INIT(raid5rec)
+{
+ grub_raid5_recover_func = grub_raid5_recover;
+}
+
+GRUB_MOD_FINI(raid5rec)
+{
+ grub_raid5_recover_func = 0;
+}
diff --git a/grub-core/disk/raid6_recover.c b/grub-core/disk/raid6_recover.c
new file mode 100644
index 0000000..75fe464
--- /dev/null
+++ b/grub-core/disk/raid6_recover.c
@@ -0,0 +1,218 @@
+/* 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/diskfilter.h>
+#include <grub/crypto.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/* x**y. */
+static grub_uint8_t powx[255 * 2];
+/* Such an s that x**s = y */
+static unsigned powx_inv[256];
+static const grub_uint8_t poly = 0x1d;
+
+static void
+grub_raid_block_mulx (unsigned mul, char *buf, grub_size_t size)
+{
+ grub_size_t i;
+ grub_uint8_t *p;
+
+ p = (grub_uint8_t *) buf;
+ for (i = 0; i < size; i++, p++)
+ if (*p)
+ *p = powx[mul + powx_inv[*p]];
+}
+
+static void
+grub_raid6_init_table (void)
+{
+ unsigned i;
+
+ grub_uint8_t cur = 1;
+ for (i = 0; i < 255; i++)
+ {
+ powx[i] = cur;
+ powx[i + 255] = cur;
+ powx_inv[cur] = i;
+ if (cur & 0x80)
+ cur = (cur << 1) ^ poly;
+ else
+ cur <<= 1;
+ }
+}
+
+static unsigned
+mod_255 (unsigned x)
+{
+ while (x > 0xff)
+ x = (x >> 8) + (x & 0xff);
+ if (x == 0xff)
+ return 0;
+ return x;
+}
+
+static grub_err_t
+raid6_recover_read_node (void *data, int disknr,
+ grub_uint64_t sector,
+ void *buf, grub_size_t size)
+{
+ struct grub_diskfilter_segment *array = data;
+
+ return grub_diskfilter_read_node (&array->nodes[disknr],
+ (grub_disk_addr_t)sector,
+ size >> GRUB_DISK_SECTOR_BITS, buf);
+}
+
+grub_err_t
+grub_raid6_recover_gen (void *data, grub_uint64_t nstripes, int disknr, int p,
+ char *buf, grub_uint64_t sector, grub_size_t size,
+ int layout, raid_recover_read_t read_func)
+{
+ int i, q, pos;
+ int bad1 = -1, bad2 = -1;
+ char *pbuf = 0, *qbuf = 0;
+
+ pbuf = grub_zalloc (size);
+ if (!pbuf)
+ goto quit;
+
+ qbuf = grub_zalloc (size);
+ if (!qbuf)
+ goto quit;
+
+ q = p + 1;
+ if (q == (int) nstripes)
+ q = 0;
+
+ pos = q + 1;
+ if (pos == (int) nstripes)
+ pos = 0;
+
+ for (i = 0; i < (int) nstripes - 2; i++)
+ {
+ int c;
+ if (layout & GRUB_RAID_LAYOUT_MUL_FROM_POS)
+ c = pos;
+ else
+ c = i;
+ if (pos == disknr)
+ bad1 = c;
+ else
+ {
+ if (!read_func (data, pos, sector, buf, size))
+ {
+ grub_crypto_xor (pbuf, pbuf, buf, size);
+ grub_raid_block_mulx (c, buf, size);
+ grub_crypto_xor (qbuf, qbuf, buf, size);
+ }
+ else
+ {
+ /* Too many bad devices */
+ if (bad2 >= 0)
+ goto quit;
+
+ bad2 = c;
+ grub_errno = GRUB_ERR_NONE;
+ }
+ }
+
+ pos++;
+ if (pos == (int) nstripes)
+ pos = 0;
+ }
+
+ /* Invalid disknr or p */
+ if (bad1 < 0)
+ goto quit;
+
+ if (bad2 < 0)
+ {
+ /* One bad device */
+ if (!read_func (data, p, sector, buf, size))
+ {
+ grub_crypto_xor (buf, buf, pbuf, size);
+ goto quit;
+ }
+
+ grub_errno = GRUB_ERR_NONE;
+ if (read_func (data, q, sector, buf, size))
+ goto quit;
+
+ grub_crypto_xor (buf, buf, qbuf, size);
+ grub_raid_block_mulx (255 - bad1, buf,
+ size);
+ }
+ else
+ {
+ /* Two bad devices */
+ unsigned c;
+
+ if (read_func (data, p, sector, buf, size))
+ goto quit;
+
+ grub_crypto_xor (pbuf, pbuf, buf, size);
+
+ if (read_func (data, q, sector, buf, size))
+ goto quit;
+
+ grub_crypto_xor (qbuf, qbuf, buf, size);
+
+ c = mod_255((255 ^ bad1)
+ + (255 ^ powx_inv[(powx[bad2 + (bad1 ^ 255)] ^ 1)]));
+ grub_raid_block_mulx (c, qbuf, size);
+
+ c = mod_255((unsigned) bad2 + c);
+ grub_raid_block_mulx (c, pbuf, size);
+
+ grub_crypto_xor (pbuf, pbuf, qbuf, size);
+ grub_memcpy (buf, pbuf, size);
+ }
+
+quit:
+ grub_free (pbuf);
+ grub_free (qbuf);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_raid6_recover (struct grub_diskfilter_segment *array, int disknr, int p,
+ char *buf, grub_disk_addr_t sector, grub_size_t size)
+{
+ return grub_raid6_recover_gen (array, array->node_count, disknr, p, buf,
+ sector, size << GRUB_DISK_SECTOR_BITS,
+ array->layout, raid6_recover_read_node);
+}
+
+GRUB_MOD_INIT(raid6rec)
+{
+ grub_raid6_init_table ();
+ grub_raid6_recover_func = grub_raid6_recover;
+}
+
+GRUB_MOD_FINI(raid6rec)
+{
+ grub_raid6_recover_func = 0;
+}
diff --git a/grub-core/disk/scsi.c b/grub-core/disk/scsi.c
new file mode 100644
index 0000000..70767cf
--- /dev/null
+++ b/grub-core/disk/scsi.c
@@ -0,0 +1,766 @@
+/* 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>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+
+static grub_scsi_dev_t grub_scsi_dev_list;
+
+const char grub_scsi_names[GRUB_SCSI_NUM_SUBSYSTEMS][5] = {
+ [GRUB_SCSI_SUBSYSTEM_USBMS] = "usb",
+ [GRUB_SCSI_SUBSYSTEM_PATA] = "ata",
+ [GRUB_SCSI_SUBSYSTEM_AHCI] = "ahci"
+};
+
+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_capacity10 (grub_scsi_t scsi)
+{
+ struct grub_scsi_read_capacity10 rc;
+ struct grub_scsi_read_capacity10_data rcd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ rc.opcode = grub_scsi_cmd_read_capacity10;
+ 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->last_block = grub_be_to_cpu32 (rcd.last_block);
+ scsi->blocksize = grub_be_to_cpu32 (rcd.blocksize);
+
+ return GRUB_ERR_NONE;
+}
+
+/* Read the capacity and block size of SCSI. */
+static grub_err_t
+grub_scsi_read_capacity16 (grub_scsi_t scsi)
+{
+ struct grub_scsi_read_capacity16 rc;
+ struct grub_scsi_read_capacity16_data rcd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ rc.opcode = grub_scsi_cmd_read_capacity16;
+ rc.lun = (scsi->lun << GRUB_SCSI_LUN_SHIFT) | 0x10;
+ rc.logical_block_addr = 0;
+ rc.alloc_len = grub_cpu_to_be32_compile_time (sizeof (rcd));
+ rc.PMI = 0;
+ rc.control = 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->last_block = grub_be_to_cpu64 (rcd.last_block);
+ 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;
+}
+
+/* Send a SCSI request for DISK: read SIZE sectors starting with
+ sector SECTOR to BUF. */
+static grub_err_t
+grub_scsi_read16 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_read16 rd;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ rd.opcode = grub_scsi_cmd_read16;
+ rd.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ rd.lba = grub_cpu_to_be64 (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;
+}
+
+/* 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, const 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;
+}
+
+#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_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
+
+/* Send a SCSI request for DISK: write the data stored in BUF to SIZE
+ sectors starting with SECTOR. */
+static grub_err_t
+grub_scsi_write16 (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ grub_scsi_t scsi;
+ struct grub_scsi_write16 wr;
+ grub_err_t err;
+ grub_err_t err_sense;
+
+ scsi = disk->data;
+
+ wr.opcode = grub_scsi_cmd_write16;
+ wr.lun = scsi->lun << GRUB_SCSI_LUN_SHIFT;
+ wr.lba = grub_cpu_to_be64 (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;
+}
+
+
+
+/* Context for grub_scsi_iterate. */
+struct grub_scsi_iterate_ctx
+{
+ grub_disk_dev_iterate_hook_t hook;
+ void *hook_data;
+};
+
+/* Helper for grub_scsi_iterate. */
+static int
+scsi_iterate (int id, int bus, int luns, void *data)
+{
+ struct grub_scsi_iterate_ctx *ctx = data;
+ int i;
+
+ /* In case of a single LUN, just return `usbX'. */
+ if (luns == 1)
+ {
+ char *sname;
+ int ret;
+ sname = grub_xasprintf ("%s%d", grub_scsi_names[id], bus);
+ if (!sname)
+ return 1;
+ ret = ctx->hook (sname, ctx->hook_data);
+ 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", grub_scsi_names[id], bus, 'a' + i);
+ if (!sname)
+ return 1;
+ ret = ctx->hook (sname, ctx->hook_data);
+ grub_free (sname);
+ if (ret)
+ return 1;
+ }
+ return 0;
+}
+
+static int
+grub_scsi_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ struct grub_scsi_iterate_ctx ctx = { hook, hook_data };
+ grub_scsi_dev_t p;
+
+ for (p = grub_scsi_dev_list; p; p = p->next)
+ if (p->iterate && (p->iterate) (scsi_iterate, &ctx, pull))
+ 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;
+ unsigned id;
+
+ 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 (id = 0; id < ARRAY_SIZE (grub_scsi_names); id++)
+ if (grub_strncmp (grub_scsi_names[id], name, nameend - name) == 0)
+ break;
+
+ if (id == ARRAY_SIZE (grub_scsi_names))
+ {
+ grub_free (scsi);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a SCSI disk");
+ }
+
+ for (p = grub_scsi_dev_list; p; p = p->next)
+ {
+ if (p->open (id, bus, scsi))
+ {
+ grub_errno = GRUB_ERR_NONE;
+ continue;
+ }
+
+ disk->id = grub_make_scsi_id (id, bus, lun);
+ disk->data = scsi;
+ scsi->dev = p;
+ scsi->lun = lun;
+ scsi->bus = bus;
+
+ grub_dprintf ("scsi", "dev opened\n");
+
+ err = grub_scsi_inquiry (scsi);
+ if (err)
+ {
+ grub_free (scsi);
+ grub_dprintf ("scsi", "inquiry failed\n");
+ return err;
+ }
+
+ grub_dprintf ("scsi", "inquiry: devtype=0x%02x removable=%d\n",
+ scsi->devtype, scsi->removable);
+
+ /* Try to be conservative about the device types
+ supported. */
+ if (scsi->devtype != grub_scsi_devtype_direct
+ && scsi->devtype != grub_scsi_devtype_cdrom)
+ {
+ grub_free (scsi);
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "unknown SCSI device");
+ }
+
+ /* According to USB MS tests specification, issue Test Unit Ready
+ * until OK */
+ maxtime = grub_get_time_ms () + 5000; /* It is safer value */
+ do
+ {
+ /* Timeout is necessary - for example in case when we have
+ * universal card reader with more LUNs and we have only
+ * one card inserted (or none), so only one LUN (or none)
+ * will be ready - and we want not to hang... */
+ if (grub_get_time_ms () > maxtime)
+ {
+ err = GRUB_ERR_READ_ERROR;
+ grub_free (scsi);
+ grub_dprintf ("scsi", "LUN is not ready - timeout\n");
+ return err;
+ }
+ err = grub_scsi_test_unit_ready (scsi);
+ }
+ while (err == GRUB_ERR_READ_ERROR);
+ /* Reset grub_errno !
+ * It is set to some error code in loop before... */
+ grub_errno = GRUB_ERR_NONE;
+
+ /* Read capacity of media */
+ err = grub_scsi_read_capacity10 (scsi);
+ if (err)
+ {
+ grub_free (scsi);
+ grub_dprintf ("scsi", "READ CAPACITY10 failed\n");
+ return err;
+ }
+
+ if (scsi->last_block == 0xffffffff)
+ {
+ err = grub_scsi_read_capacity16 (scsi);
+ if (err)
+ {
+ grub_free (scsi);
+ grub_dprintf ("scsi", "READ CAPACITY16 failed\n");
+ return err;
+ }
+ }
+
+ disk->total_sectors = scsi->last_block + 1;
+ /* PATA doesn't support more than 32K reads.
+ Not sure about AHCI and USB. If it's confirmed that either of
+ them can do bigger reads reliably this value can be moved to 'scsi'
+ structure. */
+ disk->max_agglomerate = 32768 >> (GRUB_DISK_SECTOR_BITS
+ + GRUB_DISK_CACHE_BITS);
+
+ if (scsi->blocksize & (scsi->blocksize - 1) || !scsi->blocksize)
+ {
+ grub_error (GRUB_ERR_IO, "invalid sector size %d",
+ scsi->blocksize);
+ grub_free (scsi);
+ return grub_errno;
+ }
+ for (disk->log_sector_size = 0;
+ (1U << disk->log_sector_size) < scsi->blocksize;
+ disk->log_sector_size++);
+
+ grub_dprintf ("scsi", "last_block=%" PRIuGRUB_UINT64_T ", blocksize=%u\n",
+ scsi->last_block, scsi->blocksize);
+ grub_dprintf ("scsi", "Disk total 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;
+
+ grub_err_t err;
+ /* Depending on the type, select a read function. */
+ switch (scsi->devtype)
+ {
+ case grub_scsi_devtype_direct:
+ if (sector >> 32)
+ err = grub_scsi_read16 (disk, sector, size, buf);
+ else
+ err = grub_scsi_read10 (disk, sector, size, buf);
+ if (err)
+ return err;
+ break;
+
+ case grub_scsi_devtype_cdrom:
+ if (sector >> 32)
+ err = grub_scsi_read16 (disk, sector, size, buf);
+ else
+ err = grub_scsi_read12 (disk, sector, size, buf);
+ if (err)
+ return err;
+ break;
+ }
+
+ 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,
+ grub_disk_addr_t sector,
+ grub_size_t size,
+ const char *buf)
+{
+ grub_scsi_t scsi;
+
+ scsi = disk->data;
+
+ if (scsi->devtype == grub_scsi_devtype_cdrom)
+ return grub_error (GRUB_ERR_IO, N_("cannot write to CD-ROM"));
+
+ grub_err_t err;
+ /* Depending on the type, select a read function. */
+ switch (scsi->devtype)
+ {
+ case grub_scsi_devtype_direct:
+ if (sector >> 32)
+ err = grub_scsi_write16 (disk, sector, size, buf);
+ else
+ err = grub_scsi_write10 (disk, sector, size, buf);
+ if (err)
+ return err;
+ break;
+ }
+
+ return GRUB_ERR_NONE;
+}
+
+
+static struct grub_disk_dev grub_scsi_dev =
+ {
+ .name = "scsi",
+ .id = GRUB_DISK_DEVICE_SCSI_ID,
+ .disk_iterate = grub_scsi_iterate,
+ .disk_open = grub_scsi_open,
+ .disk_close = grub_scsi_close,
+ .disk_read = grub_scsi_read,
+ .disk_write = grub_scsi_write,
+ .next = 0
+ };
+
+GRUB_MOD_INIT(scsi)
+{
+ grub_disk_dev_register (&grub_scsi_dev);
+}
+
+GRUB_MOD_FINI(scsi)
+{
+ grub_disk_dev_unregister (&grub_scsi_dev);
+}
diff --git a/grub-core/disk/uboot/ubootdisk.c b/grub-core/disk/uboot/ubootdisk.c
new file mode 100644
index 0000000..2d115a9
--- /dev/null
+++ b/grub-core/disk/uboot/ubootdisk.c
@@ -0,0 +1,307 @@
+/* ubootdisk.c - disk subsystem support for U-Boot platforms */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2013 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/err.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/partition.h>
+#include <grub/term.h>
+#include <grub/types.h>
+#include <grub/uboot/disk.h>
+#include <grub/uboot/uboot.h>
+#include <grub/uboot/api_public.h>
+
+static struct ubootdisk_data *hd_devices;
+static int hd_num;
+static int hd_max;
+
+/*
+ * grub_ubootdisk_register():
+ * Called for each disk device enumerated as part of U-Boot initialization
+ * code.
+ */
+grub_err_t
+grub_ubootdisk_register (struct device_info *newdev)
+{
+ struct ubootdisk_data *d;
+
+#define STOR_TYPE(x) ((x) & 0x0ff0)
+ switch (STOR_TYPE (newdev->type))
+ {
+ case DT_STOR_IDE:
+ case DT_STOR_SATA:
+ case DT_STOR_SCSI:
+ case DT_STOR_MMC:
+ case DT_STOR_USB:
+ /* hd */
+ if (hd_num == hd_max)
+ {
+ int new_num;
+ new_num = (hd_max ? hd_max * 2 : 1);
+ d = grub_realloc(hd_devices,
+ sizeof (struct ubootdisk_data) * new_num);
+ if (!d)
+ return grub_errno;
+ hd_devices = d;
+ hd_max = new_num;
+ }
+
+ d = &hd_devices[hd_num];
+ hd_num++;
+ break;
+ default:
+ return GRUB_ERR_BAD_DEVICE;
+ break;
+ }
+
+ d->dev = newdev;
+ d->cookie = newdev->cookie;
+ d->opencount = 0;
+
+ return 0;
+}
+
+/*
+ * uboot_disk_iterate():
+ * Iterator over enumerated disk devices.
+ */
+static int
+uboot_disk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ char buf[16];
+ int count;
+
+ switch (pull)
+ {
+ case GRUB_DISK_PULL_NONE:
+ /* "hd" - built-in mass-storage */
+ for (count = 0 ; count < hd_num; count++)
+ {
+ grub_snprintf (buf, sizeof (buf) - 1, "hd%d", count);
+ grub_dprintf ("ubootdisk", "iterating %s\n", buf);
+ if (hook (buf, hook_data))
+ return 1;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+/* Helper function for uboot_disk_open. */
+static struct ubootdisk_data *
+get_hd_device (int num)
+{
+ if (num < hd_num)
+ return &hd_devices[num];
+
+ return NULL;
+}
+
+/*
+ * uboot_disk_open():
+ * Opens a disk device already enumerated.
+ */
+static grub_err_t
+uboot_disk_open (const char *name, struct grub_disk *disk)
+{
+ struct ubootdisk_data *d;
+ struct device_info *devinfo;
+ int num;
+ int retval;
+
+ grub_dprintf ("ubootdisk", "Opening '%s'\n", name);
+
+ num = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_dprintf ("ubootdisk", "Opening '%s' failed, invalid number\n",
+ name);
+ goto fail;
+ }
+
+ if (name[1] != 'd')
+ {
+ grub_dprintf ("ubootdisk", "Opening '%s' failed, invalid name\n", name);
+ goto fail;
+ }
+
+ switch (name[0])
+ {
+ case 'h':
+ d = get_hd_device (num);
+ break;
+ default:
+ goto fail;
+ }
+
+ if (!d)
+ goto fail;
+
+ /*
+ * Subsystems may call open on the same device recursively - but U-Boot
+ * does not deal with this. So simply keep track of number of calls and
+ * return success if already open.
+ */
+ if (d->opencount > 0)
+ {
+ grub_dprintf ("ubootdisk", "(%s) already open\n", disk->name);
+ d->opencount++;
+ retval = 0;
+ }
+ else
+ {
+ retval = grub_uboot_dev_open (d->dev);
+ if (retval != 0)
+ goto fail;
+ d->opencount = 1;
+ }
+
+ grub_dprintf ("ubootdisk", "cookie: 0x%08x\n", (grub_addr_t) d->cookie);
+ disk->id = (grub_addr_t) d->cookie;
+
+ devinfo = d->dev;
+
+ d->block_size = devinfo->di_stor.block_size;
+ if (d->block_size == 0)
+ return grub_error (GRUB_ERR_IO, "no block size");
+
+ for (disk->log_sector_size = 0;
+ (1U << disk->log_sector_size) < d->block_size;
+ disk->log_sector_size++);
+
+ grub_dprintf ("ubootdisk", "(%s) blocksize=%d, log_sector_size=%d\n",
+ disk->name, d->block_size, disk->log_sector_size);
+
+ if (devinfo->di_stor.block_count)
+ disk->total_sectors = devinfo->di_stor.block_count;
+ else
+ disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
+
+ disk->data = d;
+
+ return GRUB_ERR_NONE;
+
+fail:
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such device");
+}
+
+static void
+uboot_disk_close (struct grub_disk *disk)
+{
+ struct ubootdisk_data *d;
+ int retval;
+
+ d = disk->data;
+
+ /*
+ * In mirror of open function, keep track of number of calls to close and
+ * send on to U-Boot only when opencount would decrease to 0.
+ */
+ if (d->opencount > 1)
+ {
+ grub_dprintf ("ubootdisk", "Closed (%s)\n", disk->name);
+
+ d->opencount--;
+ }
+ else if (d->opencount == 1)
+ {
+ retval = grub_uboot_dev_close (d->dev);
+ d->opencount--;
+ grub_dprintf ("ubootdisk", "closed %s (%d)\n", disk->name, retval);
+ }
+ else
+ {
+ grub_dprintf ("ubootdisk", "device %s not open!\n", disk->name);
+ }
+}
+
+/*
+ * uboot_disk_read():
+ * Called from within disk subsystem to read a sequence of blocks into the
+ * disk cache. Maps directly on top of U-Boot API, only wrap in some error
+ * handling.
+ */
+static grub_err_t
+uboot_disk_read (struct grub_disk *disk,
+ grub_disk_addr_t offset, grub_size_t numblocks, char *buf)
+{
+ struct ubootdisk_data *d;
+ grub_size_t real_size;
+ int retval;
+
+ d = disk->data;
+
+ retval = grub_uboot_dev_read (d->dev, buf, numblocks, offset, &real_size);
+ grub_dprintf ("ubootdisk",
+ "retval=%d, numblocks=%d, real_size=%llu, sector=%llu\n",
+ retval, numblocks, (grub_uint64_t) real_size,
+ (grub_uint64_t) offset);
+ if (retval != 0)
+ return grub_error (GRUB_ERR_IO, "U-Boot disk read error");
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+uboot_disk_write (struct grub_disk *disk,
+ grub_disk_addr_t offset, grub_size_t numblocks, const char *buf)
+{
+ struct ubootdisk_data *d;
+ int retval;
+
+ d = disk->data;
+
+ retval = grub_uboot_dev_write (d->dev, buf, numblocks, offset);
+ grub_dprintf ("ubootdisk",
+ "retval=%d, numblocks=%d, sector=%llu\n",
+ retval, numblocks, (grub_uint64_t) offset);
+
+ if (retval != 0)
+ return grub_error (GRUB_ERR_IO, "U-Boot disk write error");
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_disk_dev grub_ubootdisk_dev = {
+ .name = "ubootdisk",
+ .id = GRUB_DISK_DEVICE_UBOOTDISK_ID,
+ .disk_iterate = uboot_disk_iterate,
+ .disk_open = uboot_disk_open,
+ .disk_close = uboot_disk_close,
+ .disk_read = uboot_disk_read,
+ .disk_write = uboot_disk_write,
+ .next = 0
+};
+
+void
+grub_ubootdisk_init (void)
+{
+ grub_disk_dev_register (&grub_ubootdisk_dev);
+}
+
+void
+grub_ubootdisk_fini (void)
+{
+ grub_disk_dev_unregister (&grub_ubootdisk_dev);
+}
diff --git a/grub-core/disk/usbms.c b/grub-core/disk/usbms.c
new file mode 100644
index 0000000..380ca4c
--- /dev/null
+++ b/grub-core/disk/usbms.c
@@ -0,0 +1,660 @@
+/* usbms.c - USB Mass Storage Support. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/mm.h>
+#include <grub/usb.h>
+#include <grub/scsi.h>
+#include <grub/scsicmd.h>
+#include <grub/misc.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define GRUB_USBMS_DIRECTION_BIT 7
+
+/* Length of CBI command should be always 12 bytes */
+#define GRUB_USBMS_CBI_CMD_SIZE 12
+/* CBI class-specific USB request ADSC - it sends CBI (scsi) command to
+ * device in DATA stage */
+#define GRUB_USBMS_CBI_ADSC_REQ 0x00
+
+/* 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];
+} GRUB_PACKED;
+
+struct grub_usbms_csw
+{
+ grub_uint32_t signature;
+ grub_uint32_t tag;
+ grub_uint32_t residue;
+ grub_uint8_t status;
+} GRUB_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 subclass;
+ int protocol;
+ struct grub_usb_desc_endp *intrpt;
+};
+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_usb_err_t
+grub_usbms_cbi_cmd (grub_usb_device_t dev, int interface,
+ grub_uint8_t *cbicb)
+{
+ return grub_usb_control_msg (dev,
+ GRUB_USB_REQTYPE_CLASS_INTERFACE_OUT,
+ GRUB_USBMS_CBI_ADSC_REQ, 0, interface,
+ GRUB_USBMS_CBI_CMD_SIZE, (char*)cbicb);
+}
+
+static grub_usb_err_t
+grub_usbms_cbi_reset (grub_usb_device_t dev, int interface)
+{
+ /* Prepare array with Command Block Reset (=CBR) */
+ /* CBI specific communication reset command should be send to device
+ * via CBI USB class specific request ADCS */
+ struct grub_cbi_reset
+ {
+ grub_uint8_t opcode; /* 0x1d = SEND DIAGNOSTIC */
+ grub_uint8_t lun; /* 7-5 LUN, 4-0 flags - for CBR always = 0x04 */
+ grub_uint8_t pad[10];
+ /* XXX: There is collision between CBI and UFI specifications:
+ * CBI says 0xff, UFI says 0x00 ... probably it does
+ * not matter ... (?) */
+ } cbicb = { 0x1d, 0x04,
+ { 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff }
+ };
+
+ return grub_usbms_cbi_cmd (dev, interface, (grub_uint8_t *)&cbicb);
+}
+
+static grub_usb_err_t
+grub_usbms_bo_reset (grub_usb_device_t dev, int interface)
+{
+ return grub_usb_control_msg (dev, 0x21, 255, 0, interface, 0, 0);
+}
+
+static grub_usb_err_t
+grub_usbms_reset (grub_usbms_dev_t dev)
+{
+ if (dev->protocol == GRUB_USBMS_PROTOCOL_BULK)
+ return grub_usbms_bo_reset (dev->dev, dev->interface);
+ else
+ return grub_usbms_cbi_reset (dev->dev, dev->interface);
+}
+
+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 = GRUB_USB_ERR_NONE;
+
+ grub_boot_time ("Attaching USB mass storage");
+
+ 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
+ && interf->protocol != GRUB_USBMS_PROTOCOL_CBI
+ && interf->protocol != GRUB_USBMS_PROTOCOL_CB))
+ 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_usbms_devices[curnum]->subclass = interf->subclass;
+ grub_usbms_devices[curnum]->protocol = interf->protocol;
+
+ 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;
+ else if (!(endp->endp_addr & 128) && (endp->attrib & 3) == 2)
+ /* Bulk OUT endpoint. */
+ grub_usbms_devices[curnum]->out = endp;
+ else if ((endp->endp_addr & 128) && (endp->attrib & 3) == 3)
+ /* Interrupt (IN) endpoint. */
+ grub_usbms_devices[curnum]->intrpt = endp;
+ }
+
+ if (!grub_usbms_devices[curnum]->in || !grub_usbms_devices[curnum]->out
+ || ((grub_usbms_devices[curnum]->protocol == GRUB_USBMS_PROTOCOL_CBI)
+ && !grub_usbms_devices[curnum]->intrpt))
+ {
+ 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. */
+ if (grub_usbms_devices[curnum]->protocol == GRUB_USBMS_PROTOCOL_BULK)
+ { /* Only Bulk only devices support Get Max LUN command */
+ 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;
+ }
+ else
+ /* XXX: Does CBI devices support multiple LUNs ?
+ * I.e., should we detect number of device's LUNs ? (How?) */
+ grub_usbms_devices[curnum]->luns = 1;
+
+ grub_dprintf ("usbms", "alive\n");
+
+ usbdev->config[configno].interf[interfno].detach_hook = grub_usbms_detach;
+
+ grub_boot_time ("Attached USB mass storage");
+
+#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 (grub_scsi_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ unsigned i;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ grub_usb_poll_devices (1);
+
+ for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
+ if (grub_usbms_devices[i])
+ {
+ if (hook (GRUB_SCSI_SUBSYSTEM_USBMS, i, grub_usbms_devices[i]->luns,
+ hook_data))
+ return 1;
+ }
+
+ return 0;
+}
+
+static grub_err_t
+grub_usbms_transfer_bo (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;
+
+ tag++;
+
+ 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_compile_time (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,
+ sizeof (cbw), (char *) &cbw);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ {
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ goto CheckCSW;
+ }
+ goto retry;
+ }
+
+ /* Read/write the data, (maybe) according to specification. */
+ if (size && (read_write == 0))
+ {
+ err = grub_usb_bulk_read (dev->dev, dev->in, 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, 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,
+ 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,
+ sizeof (status), (char *) &status);
+ if (errCSW)
+ { /* Bulk-only reset device. */
+ grub_dprintf ("usb", "Bulk-only reset device - errCSW\n");
+ grub_usbms_reset (dev);
+ 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_compile_time(0x53425355)))
+ { /* Bulk-only reset device. */
+ grub_dprintf ("usb", "Bulk-only reset device - bad status\n");
+ grub_usbms_reset (dev);
+ 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_transfer_cbi (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd,
+ grub_size_t size, char *buf, int read_write)
+{
+ grub_usbms_dev_t dev = (grub_usbms_dev_t) scsi->data;
+ int retrycnt = 3 + 1;
+ grub_usb_err_t err = GRUB_USB_ERR_NONE;
+ grub_uint8_t cbicb[GRUB_USBMS_CBI_CMD_SIZE];
+ grub_uint16_t status;
+
+ retry:
+ retrycnt--;
+ if (retrycnt == 0)
+ return grub_error (GRUB_ERR_IO, "USB Mass Storage CBI failed");
+
+ /* Setup the request. */
+ grub_memset (cbicb, 0, sizeof (cbicb));
+ grub_memcpy (cbicb, cmd,
+ cmdsize >= GRUB_USBMS_CBI_CMD_SIZE
+ ? GRUB_USBMS_CBI_CMD_SIZE
+ : cmdsize);
+
+ /* Debug print of CBIcb content. */
+ grub_dprintf ("usb", "cbicb:\n %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ cbicb[ 0], cbicb[ 1], cbicb[ 2], cbicb[ 3],
+ cbicb[ 4], cbicb[ 5], cbicb[ 6], cbicb[ 7],
+ cbicb[ 8], cbicb[ 9], cbicb[10], cbicb[11]);
+
+ /* Write the request.
+ * XXX: Error recovery is maybe not correct. */
+ err = grub_usbms_cbi_cmd (dev->dev, dev->interface, cbicb);
+ if (err)
+ {
+ grub_dprintf ("usb", "CBI cmdcb setup err=%d\n", err);
+ if (err == GRUB_USB_ERR_STALL)
+ {
+ /* Stall in this place probably means bad or unsupported
+ * command, so we will not try it again. */
+ return grub_error (GRUB_ERR_IO, "USB Mass Storage CBI request failed");
+ }
+ else if (dev->protocol == GRUB_USBMS_PROTOCOL_CBI)
+ {
+ /* Try to get status from interrupt pipe */
+ err = grub_usb_bulk_read (dev->dev, dev->intrpt,
+ 2, (char*)&status);
+ grub_dprintf ("usb", "CBI cmdcb setup status: err=%d, status=0x%x\n", err, status);
+ }
+ /* Any other error could be transport problem, try it again */
+ goto retry;
+ }
+
+ /* Read/write the data, (maybe) according to specification. */
+ if (size && (read_write == 0))
+ {
+ err = grub_usb_bulk_read (dev->dev, dev->in, size, buf);
+ grub_dprintf ("usb", "read: %d\n", err);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ goto retry;
+ }
+ }
+ else if (size)
+ {
+ err = grub_usb_bulk_write (dev->dev, dev->out, size, buf);
+ grub_dprintf ("usb", "write: %d\n", err);
+ if (err)
+ {
+ if (err == GRUB_USB_ERR_STALL)
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ goto retry;
+ }
+ }
+
+ /* XXX: It is not clear to me yet, how to check status of CBI
+ * data transfer on devices without interrupt pipe.
+ * AFAIK there is probably no status phase to indicate possibly
+ * bad transported data.
+ * Maybe we should do check on higher level, i.e. issue RequestSense
+ * command (we do it already in scsi.c) and check returned values
+ * (we do not it yet) - ? */
+ if (dev->protocol == GRUB_USBMS_PROTOCOL_CBI)
+ { /* Check status in interrupt pipe */
+ err = grub_usb_bulk_read (dev->dev, dev->intrpt,
+ 2, (char*)&status);
+ grub_dprintf ("usb", "read status: %d\n", err);
+ if (err)
+ {
+ /* Try to reset device, because it is probably not standard
+ * situation */
+ grub_usbms_reset (dev);
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->intrpt->endp_addr);
+ goto retry;
+ }
+ if (dev->subclass == GRUB_USBMS_SUBCLASS_UFI)
+ {
+ /* These devices should return bASC and bASCQ */
+ if (status != 0)
+ /* Some error, currently we don't care what it is... */
+ goto retry;
+ }
+ else if (dev->subclass == GRUB_USBMS_SUBCLASS_RBC)
+ {
+ /* XXX: I don't understand what returns RBC subclass devices,
+ * so I don't check it - maybe somebody helps ? */
+ }
+ else
+ {
+ /* Any other device should return bType = 0 and some bValue */
+ if (status & 0xff)
+ return grub_error (GRUB_ERR_IO, "USB Mass Storage CBI status type != 0");
+ status = (status & 0x0300) >> 8;
+ switch (status)
+ {
+ case 0 : /* OK */
+ break;
+ case 1 : /* Fail */
+ goto retry;
+ break;
+ case 2 : /* Phase error */
+ case 3 : /* Persistent Failure */
+ grub_dprintf ("usb", "CBI reset device - phase error or persistent failure\n");
+ grub_usbms_reset (dev);
+ grub_usb_clear_halt (dev->dev, dev->in->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->out->endp_addr);
+ grub_usb_clear_halt (dev->dev, dev->intrpt->endp_addr);
+ goto retry;
+ break;
+ }
+ }
+ }
+
+ if (err)
+ return grub_error (GRUB_ERR_IO, "USB error %d", err);
+
+ return GRUB_ERR_NONE;
+}
+
+
+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)
+{
+ grub_usbms_dev_t dev = (grub_usbms_dev_t) scsi->data;
+
+ if (dev->protocol == GRUB_USBMS_PROTOCOL_BULK)
+ return grub_usbms_transfer_bo (scsi, cmdsize, cmd, size, buf,
+ read_write);
+ else
+ return grub_usbms_transfer_cbi (scsi, cmdsize, cmd, size, buf,
+ read_write);
+}
+
+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, const char *buf)
+{
+ return grub_usbms_transfer (scsi, cmdsize, cmd, size, (char *) buf, 1);
+}
+
+static grub_err_t
+grub_usbms_open (int id, int devnum, struct grub_scsi *scsi)
+{
+ if (id != GRUB_SCSI_SUBSYSTEM_USBMS)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE,
+ "not USB Mass Storage device");
+
+ if (!grub_usbms_devices[devnum])
+ grub_usb_poll_devices (1);
+
+ 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 =
+ {
+ .iterate = grub_usbms_iterate,
+ .open = grub_usbms_open,
+ .read = grub_usbms_read,
+ .write = grub_usbms_write
+ };
+
+static struct grub_usb_attach_desc attach_hook =
+{
+ .class = GRUB_USB_CLASS_MASS_STORAGE,
+ .hook = grub_usbms_attach
+};
+
+GRUB_MOD_INIT(usbms)
+{
+ grub_usb_register_attach_hook_class (&attach_hook);
+ grub_scsi_dev_register (&grub_usbms_dev);
+}
+
+GRUB_MOD_FINI(usbms)
+{
+ unsigned i;
+ for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++)
+ {
+ grub_usbms_devices[i]->dev->config[grub_usbms_devices[i]->config]
+ .interf[grub_usbms_devices[i]->interface].detach_hook = 0;
+ grub_usbms_devices[i]->dev->config[grub_usbms_devices[i]->config]
+ .interf[grub_usbms_devices[i]->interface].attached = 0;
+ }
+ grub_usb_unregister_attach_hook_class (&attach_hook);
+ grub_scsi_dev_unregister (&grub_usbms_dev);
+}
diff --git a/grub-core/disk/xen/xendisk.c b/grub-core/disk/xen/xendisk.c
new file mode 100644
index 0000000..d6612ee
--- /dev/null
+++ b/grub-core/disk/xen/xendisk.c
@@ -0,0 +1,485 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2013 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/mm.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/err.h>
+#include <grub/term.h>
+#include <grub/i18n.h>
+#include <grub/xen.h>
+#include <grub/time.h>
+#include <xen/io/blkif.h>
+
+struct virtdisk
+{
+ int handle;
+ char *fullname;
+ char *backend_dir;
+ char *frontend_dir;
+ struct blkif_sring *shared_page;
+ struct blkif_front_ring ring;
+ grub_xen_grant_t grant;
+ grub_xen_evtchn_t evtchn;
+ void *dma_page;
+ grub_xen_grant_t dma_grant;
+ struct virtdisk *compat_next;
+};
+
+#define xen_wmb() mb()
+#define xen_mb() mb()
+
+static struct virtdisk *virtdisks;
+static grub_size_t vdiskcnt;
+struct virtdisk *compat_head;
+
+static int
+grub_virtdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+ grub_disk_pull_t pull)
+{
+ grub_size_t i;
+
+ if (pull != GRUB_DISK_PULL_NONE)
+ return 0;
+
+ for (i = 0; i < vdiskcnt; i++)
+ if (hook (virtdisks[i].fullname, hook_data))
+ return 1;
+ return 0;
+}
+
+static grub_err_t
+grub_virtdisk_open (const char *name, grub_disk_t disk)
+{
+ int i;
+ grub_uint32_t secsize;
+ char fdir[200];
+ char *buf;
+ int num = -1;
+ struct virtdisk *vd;
+
+ /* For compatibility with pv-grub legacy menu.lst accept hdX as disk name */
+ if (name[0] == 'h' && name[1] == 'd' && name[2])
+ {
+ num = grub_strtoul (name + 2, 0, 10);
+ if (grub_errno)
+ {
+ grub_errno = 0;
+ num = -1;
+ }
+ }
+ for (i = 0, vd = compat_head; vd; vd = vd->compat_next, i++)
+ if (i == num || grub_strcmp (name, vd->fullname) == 0)
+ break;
+ if (!vd)
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a virtdisk");
+ disk->data = vd;
+ disk->id = vd - virtdisks;
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/sectors", vd->backend_dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ if (!buf)
+ return grub_errno;
+ disk->total_sectors = grub_strtoull (buf, 0, 10);
+ if (grub_errno)
+ return grub_errno;
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/sector-size", vd->backend_dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ if (!buf)
+ return grub_errno;
+ secsize = grub_strtoull (buf, 0, 10);
+ if (grub_errno)
+ return grub_errno;
+
+ if ((secsize & (secsize - 1)) || !secsize || secsize < 512
+ || secsize > GRUB_XEN_PAGE_SIZE)
+ return grub_error (GRUB_ERR_IO, "unsupported sector size %d", secsize);
+
+ for (disk->log_sector_size = 0;
+ (1U << disk->log_sector_size) < secsize; disk->log_sector_size++);
+
+ disk->total_sectors >>= disk->log_sector_size - 9;
+
+ return GRUB_ERR_NONE;
+}
+
+static void
+grub_virtdisk_close (grub_disk_t disk __attribute__ ((unused)))
+{
+}
+
+static grub_err_t
+grub_virtdisk_read (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, char *buf)
+{
+ struct virtdisk *data = disk->data;
+
+ while (size)
+ {
+ grub_size_t cur;
+ struct blkif_request *req;
+ struct blkif_response *resp;
+ int sta = 0;
+ struct evtchn_send send;
+ cur = size;
+ if (cur > (unsigned) (GRUB_XEN_PAGE_SIZE >> disk->log_sector_size))
+ cur = GRUB_XEN_PAGE_SIZE >> disk->log_sector_size;
+ while (RING_FULL (&data->ring))
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ req = RING_GET_REQUEST (&data->ring, data->ring.req_prod_pvt);
+ req->operation = BLKIF_OP_READ;
+ req->nr_segments = 1;
+ req->handle = data->handle;
+ req->id = 0;
+ req->sector_number = sector << (disk->log_sector_size - 9);
+ req->seg[0].gref = data->dma_grant;
+ req->seg[0].first_sect = 0;
+ req->seg[0].last_sect = (cur << (disk->log_sector_size - 9)) - 1;
+ data->ring.req_prod_pvt++;
+ RING_PUSH_REQUESTS (&data->ring);
+ mb ();
+ send.port = data->evtchn;
+ grub_xen_event_channel_op (EVTCHNOP_send, &send);
+
+ while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring))
+ {
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ mb ();
+ }
+ while (1)
+ {
+ int wtd;
+ RING_FINAL_CHECK_FOR_RESPONSES (&data->ring, wtd);
+ if (!wtd)
+ break;
+ resp = RING_GET_RESPONSE (&data->ring, data->ring.rsp_cons);
+ data->ring.rsp_cons++;
+ if (resp->status)
+ sta = resp->status;
+ }
+ if (sta)
+ return grub_error (GRUB_ERR_IO, "read failed");
+ grub_memcpy (buf, data->dma_page, cur << disk->log_sector_size);
+ size -= cur;
+ sector += cur;
+ buf += cur << disk->log_sector_size;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_virtdisk_write (grub_disk_t disk, grub_disk_addr_t sector,
+ grub_size_t size, const char *buf)
+{
+ struct virtdisk *data = disk->data;
+
+ while (size)
+ {
+ grub_size_t cur;
+ struct blkif_request *req;
+ struct blkif_response *resp;
+ int sta = 0;
+ struct evtchn_send send;
+ cur = size;
+ if (cur > (unsigned) (GRUB_XEN_PAGE_SIZE >> disk->log_sector_size))
+ cur = GRUB_XEN_PAGE_SIZE >> disk->log_sector_size;
+
+ grub_memcpy (data->dma_page, buf, cur << disk->log_sector_size);
+
+ while (RING_FULL (&data->ring))
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ req = RING_GET_REQUEST (&data->ring, data->ring.req_prod_pvt);
+ req->operation = BLKIF_OP_WRITE;
+ req->nr_segments = 1;
+ req->handle = data->handle;
+ req->id = 0;
+ req->sector_number = sector << (disk->log_sector_size - 9);
+ req->seg[0].gref = data->dma_grant;
+ req->seg[0].first_sect = 0;
+ req->seg[0].last_sect = (cur << (disk->log_sector_size - 9)) - 1;
+ data->ring.req_prod_pvt++;
+ RING_PUSH_REQUESTS (&data->ring);
+ mb ();
+ send.port = data->evtchn;
+ grub_xen_event_channel_op (EVTCHNOP_send, &send);
+
+ while (!RING_HAS_UNCONSUMED_RESPONSES (&data->ring))
+ {
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ mb ();
+ }
+ while (1)
+ {
+ int wtd;
+ RING_FINAL_CHECK_FOR_RESPONSES (&data->ring, wtd);
+ if (!wtd)
+ break;
+ resp = RING_GET_RESPONSE (&data->ring, data->ring.rsp_cons);
+ data->ring.rsp_cons++;
+ if (resp->status)
+ sta = resp->status;
+ }
+ if (sta)
+ return grub_error (GRUB_ERR_IO, "write failed");
+ size -= cur;
+ sector += cur;
+ buf += cur << disk->log_sector_size;
+ }
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_disk_dev grub_virtdisk_dev = {
+ .name = "xen",
+ .id = GRUB_DISK_DEVICE_XEN,
+ .disk_iterate = grub_virtdisk_iterate,
+ .disk_open = grub_virtdisk_open,
+ .disk_close = grub_virtdisk_close,
+ .disk_read = grub_virtdisk_read,
+ .disk_write = grub_virtdisk_write,
+ .next = 0
+};
+
+static int
+count (const char *dir __attribute__ ((unused)), void *data)
+{
+ grub_size_t *ctr = data;
+ (*ctr)++;
+
+ return 0;
+}
+
+static int
+fill (const char *dir, void *data)
+{
+ grub_size_t *ctr = data;
+ domid_t dom;
+ /* "dir" is just a number, at most 19 characters. */
+ char fdir[200];
+ char num[20];
+ grub_err_t err;
+ void *buf;
+ struct evtchn_alloc_unbound alloc_unbound;
+ struct virtdisk **prev = &compat_head, *vd = compat_head;
+
+ /* Shouldn't happen unles some hotplug happened. */
+ if (vdiskcnt >= *ctr)
+ return 1;
+ virtdisks[vdiskcnt].handle = grub_strtoul (dir, 0, 10);
+ if (grub_errno)
+ {
+ grub_errno = 0;
+ return 0;
+ }
+ virtdisks[vdiskcnt].fullname = 0;
+ virtdisks[vdiskcnt].backend_dir = 0;
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend", dir);
+ virtdisks[vdiskcnt].backend_dir = grub_xenstore_get_file (fdir, NULL);
+ if (!virtdisks[vdiskcnt].backend_dir)
+ goto out_fail_1;
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/dev",
+ virtdisks[vdiskcnt].backend_dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ if (!buf)
+ {
+ grub_errno = 0;
+ virtdisks[vdiskcnt].fullname = grub_xasprintf ("xenid/%s", dir);
+ }
+ else
+ {
+ virtdisks[vdiskcnt].fullname = grub_xasprintf ("xen/%s", (char *) buf);
+ grub_free (buf);
+ }
+ if (!virtdisks[vdiskcnt].fullname)
+ goto out_fail_1;
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/backend-id", dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ if (!buf)
+ goto out_fail_1;
+
+ dom = grub_strtoul (buf, 0, 10);
+ grub_free (buf);
+ if (grub_errno)
+ goto out_fail_1;
+
+ virtdisks[vdiskcnt].shared_page =
+ grub_xen_alloc_shared_page (dom, &virtdisks[vdiskcnt].grant);
+ if (!virtdisks[vdiskcnt].shared_page)
+ goto out_fail_1;
+
+ virtdisks[vdiskcnt].dma_page =
+ grub_xen_alloc_shared_page (dom, &virtdisks[vdiskcnt].dma_grant);
+ if (!virtdisks[vdiskcnt].dma_page)
+ goto out_fail_2;
+
+ alloc_unbound.dom = DOMID_SELF;
+ alloc_unbound.remote_dom = dom;
+
+ grub_xen_event_channel_op (EVTCHNOP_alloc_unbound, &alloc_unbound);
+ virtdisks[vdiskcnt].evtchn = alloc_unbound.port;
+
+ SHARED_RING_INIT (virtdisks[vdiskcnt].shared_page);
+ FRONT_RING_INIT (&virtdisks[vdiskcnt].ring, virtdisks[vdiskcnt].shared_page,
+ GRUB_XEN_PAGE_SIZE);
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/ring-ref", dir);
+ grub_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].grant);
+ err = grub_xenstore_write_file (fdir, num, grub_strlen (num));
+ if (err)
+ goto out_fail_3;
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/event-channel", dir);
+ grub_snprintf (num, sizeof (num), "%u", virtdisks[vdiskcnt].evtchn);
+ err = grub_xenstore_write_file (fdir, num, grub_strlen (num));
+ if (err)
+ goto out_fail_3;
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/protocol", dir);
+ err = grub_xenstore_write_file (fdir, XEN_IO_PROTO_ABI_NATIVE,
+ grub_strlen (XEN_IO_PROTO_ABI_NATIVE));
+ if (err)
+ goto out_fail_3;
+
+ struct gnttab_dump_table dt;
+ dt.dom = DOMID_SELF;
+ grub_xen_grant_table_op (GNTTABOP_dump_table, (void *) &dt, 1);
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s/state", dir);
+ err = grub_xenstore_write_file (fdir, "3", 1);
+ if (err)
+ goto out_fail_3;
+
+ while (1)
+ {
+ grub_snprintf (fdir, sizeof (fdir), "%s/state",
+ virtdisks[vdiskcnt].backend_dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ if (!buf)
+ goto out_fail_3;
+ if (grub_strcmp (buf, "2") != 0)
+ break;
+ grub_free (buf);
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ }
+ grub_dprintf ("xen", "state=%s\n", (char *) buf);
+ grub_free (buf);
+
+ grub_snprintf (fdir, sizeof (fdir), "device/vbd/%s", dir);
+
+ virtdisks[vdiskcnt].frontend_dir = grub_strdup (fdir);
+
+ /* For compatibility with pv-grub maintain linked list sorted by handle
+ value in increasing order. This allows mapping of (hdX) disk names
+ from legacy menu.lst */
+ while (vd)
+ {
+ if (vd->handle > virtdisks[vdiskcnt].handle)
+ break;
+ prev = &vd->compat_next;
+ vd = vd->compat_next;
+ }
+ virtdisks[vdiskcnt].compat_next = vd;
+ *prev = &virtdisks[vdiskcnt];
+
+ vdiskcnt++;
+ return 0;
+
+out_fail_3:
+ grub_xen_free_shared_page (virtdisks[vdiskcnt].dma_page);
+out_fail_2:
+ grub_xen_free_shared_page (virtdisks[vdiskcnt].shared_page);
+out_fail_1:
+ grub_free (virtdisks[vdiskcnt].backend_dir);
+ grub_free (virtdisks[vdiskcnt].fullname);
+
+ grub_errno = 0;
+ return 0;
+}
+
+void
+grub_xendisk_init (void)
+{
+ grub_size_t ctr = 0;
+ if (grub_xenstore_dir ("device/vbd", count, &ctr))
+ grub_errno = 0;
+
+ if (!ctr)
+ return;
+
+ virtdisks = grub_calloc (ctr, sizeof (virtdisks[0]));
+ if (!virtdisks)
+ return;
+ if (grub_xenstore_dir ("device/vbd", fill, &ctr))
+ grub_errno = 0;
+
+ grub_disk_dev_register (&grub_virtdisk_dev);
+}
+
+void
+grub_xendisk_fini (void)
+{
+ char fdir[200];
+ unsigned i;
+
+ for (i = 0; i < vdiskcnt; i++)
+ {
+ char *buf;
+ struct evtchn_close close_op = {.port = virtdisks[i].evtchn };
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/state",
+ virtdisks[i].frontend_dir);
+ grub_xenstore_write_file (fdir, "6", 1);
+
+ while (1)
+ {
+ grub_snprintf (fdir, sizeof (fdir), "%s/state",
+ virtdisks[i].backend_dir);
+ buf = grub_xenstore_get_file (fdir, NULL);
+ grub_dprintf ("xen", "state=%s\n", (char *) buf);
+
+ if (!buf || grub_strcmp (buf, "6") == 0)
+ break;
+ grub_free (buf);
+ grub_xen_sched_op (SCHEDOP_yield, 0);
+ }
+ grub_free (buf);
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/ring-ref",
+ virtdisks[i].frontend_dir);
+ grub_xenstore_write_file (fdir, NULL, 0);
+
+ grub_snprintf (fdir, sizeof (fdir), "%s/event-channel",
+ virtdisks[i].frontend_dir);
+ grub_xenstore_write_file (fdir, NULL, 0);
+
+ grub_xen_free_shared_page (virtdisks[i].dma_page);
+ grub_xen_free_shared_page (virtdisks[i].shared_page);
+
+ grub_xen_event_channel_op (EVTCHNOP_close, &close_op);
+
+ /* Prepare for handoff. */
+ grub_snprintf (fdir, sizeof (fdir), "%s/state",
+ virtdisks[i].frontend_dir);
+ grub_xenstore_write_file (fdir, "1", 1);
+ }
+}