diff options
Diffstat (limited to 'grub-core/disk')
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, ®s); + + for (drive = 0; drive < 2; drive++) + { + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT | GRUB_CPU_INT_FLAGS_CARRY; + regs.edx = drive; + + /* call GET DISK TYPE */ + regs.eax = 0x1500; + grub_bios_interrupt (0x13, ®s); + if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY) + break; + + /* check if this drive exists */ + if (!(regs.eax & 0x300)) + break; + } + + return drive; +} + +/* + * Call IBM/MS INT13 Extensions (int 13 %ah=AH) for DRIVE. DAP + * is passed for disk address packet. If an error occurs, return + * non-zero, otherwise zero. + */ + +static int +grub_biosdisk_rw_int13_extensions (int ah, int drive, void *dap) +{ + struct grub_bios_int_registers regs; + regs.eax = ah << 8; + /* compute the address of disk_address_packet */ + regs.ds = (((grub_addr_t) dap) & 0xffff0000) >> 4; + regs.esi = (((grub_addr_t) dap) & 0xffff); + regs.edx = drive; + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + + grub_bios_interrupt (0x13, ®s); + return (regs.eax >> 8) & 0xff; +} + +/* + * Call standard and old INT13 (int 13 %ah=AH) for DRIVE. Read/write + * NSEC sectors from COFF/HOFF/SOFF into SEGMENT. If an error occurs, + * return non-zero, otherwise zero. + */ +static int +grub_biosdisk_rw_standard (int ah, int drive, int coff, int hoff, + int soff, int nsec, int segment) +{ + int ret, i; + + /* Try 3 times. */ + for (i = 0; i < 3; i++) + { + struct grub_bios_int_registers regs; + + /* set up CHS information */ + /* set %ch to low eight bits of cylinder */ + regs.ecx = (coff << 8) & 0xff00; + /* set bits 6-7 of %cl to high two bits of cylinder */ + regs.ecx |= (coff >> 2) & 0xc0; + /* set bits 0-5 of %cl to sector */ + regs.ecx |= soff & 0x3f; + + /* set %dh to head and %dl to drive */ + regs.edx = (drive & 0xff) | ((hoff << 8) & 0xff00); + /* set %ah to AH */ + regs.eax = (ah << 8) & 0xff00; + /* set %al to NSEC */ + regs.eax |= nsec & 0xff; + + regs.ebx = 0; + regs.es = segment; + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + + grub_bios_interrupt (0x13, ®s); + /* check if successful */ + if (!(regs.flags & GRUB_CPU_INT_FLAGS_CARRY)) + return 0; + + /* save return value */ + ret = regs.eax >> 8; + + /* if fail, reset the disk system */ + regs.eax = 0; + regs.edx = (drive & 0xff); + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + grub_bios_interrupt (0x13, ®s); + } + return ret; +} + +/* + * Check if LBA is supported for DRIVE. If it is supported, then return + * the major version of extensions, otherwise zero. + */ +static int +grub_biosdisk_check_int13_extensions (int drive) +{ + struct grub_bios_int_registers regs; + + regs.edx = drive & 0xff; + regs.eax = 0x4100; + regs.ebx = 0x55aa; + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + grub_bios_interrupt (0x13, ®s); + + if (regs.flags & GRUB_CPU_INT_FLAGS_CARRY) + return 0; + + if ((regs.ebx & 0xffff) != 0xaa55) + return 0; + + /* check if AH=0x42 is supported */ + if (!(regs.ecx & 1)) + return 0; + + return (regs.eax >> 8) & 0xff; +} + +/* + * Return the geometry of DRIVE in CYLINDERS, HEADS and SECTORS. If an + * error occurs, then return non-zero, otherwise zero. + */ +static int +grub_biosdisk_get_diskinfo_standard (int drive, + unsigned long *cylinders, + unsigned long *heads, + unsigned long *sectors) +{ + struct grub_bios_int_registers regs; + + regs.eax = 0x0800; + regs.edx = drive & 0xff; + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + grub_bios_interrupt (0x13, ®s); + + /* Check if unsuccessful. Ignore return value if carry isn't set to + workaround some buggy BIOSes. */ + if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0)) + return (regs.eax & 0xff00) >> 8; + + /* bogus BIOSes may not return an error number */ + /* 0 sectors means no disk */ + if (!(regs.ecx & 0x3f)) + /* XXX 0x60 is one of the unused error numbers */ + return 0x60; + + /* the number of heads is counted from zero */ + *heads = ((regs.edx >> 8) & 0xff) + 1; + *cylinders = (((regs.ecx >> 8) & 0xff) | ((regs.ecx << 2) & 0x0300)) + 1; + *sectors = regs.ecx & 0x3f; + return 0; +} + +static int +grub_biosdisk_get_diskinfo_real (int drive, void *drp, grub_uint16_t ax) +{ + struct grub_bios_int_registers regs; + + regs.eax = ax; + + /* compute the address of drive parameters */ + regs.esi = ((grub_addr_t) drp) & 0xf; + regs.ds = ((grub_addr_t) drp) >> 4; + regs.edx = drive & 0xff; + + regs.flags = GRUB_CPU_INT_FLAGS_DEFAULT; + grub_bios_interrupt (0x13, ®s); + + /* Check if unsuccessful. Ignore return value if carry isn't set to + workaround some buggy BIOSes. */ + if ((regs.flags & GRUB_CPU_INT_FLAGS_CARRY) && ((regs.eax & 0xff00) != 0)) + return (regs.eax & 0xff00) >> 8; + + return 0; +} + +/* + * Return the cdrom information of DRIVE in CDRP. If an error occurs, + * then return non-zero, otherwise zero. + */ +static int +grub_biosdisk_get_cdinfo_int13_extensions (int drive, void *cdrp) +{ + return grub_biosdisk_get_diskinfo_real (drive, cdrp, 0x4b01); +} + +/* + * Return the geometry of DRIVE in a drive parameters, DRP. If an error + * occurs, then return non-zero, otherwise zero. + */ +static int +grub_biosdisk_get_diskinfo_int13_extensions (int drive, void *drp) +{ + return grub_biosdisk_get_diskinfo_real (drive, drp, 0x4800); +} + +static int +grub_biosdisk_get_drive (const char *name) +{ + unsigned long drive; + + if (name[0] == 'c' && name[1] == 'd' && name[2] == 0 && cd_drive) + return cd_drive; + + if ((name[0] != 'f' && name[0] != 'h') || name[1] != 'd') + goto fail; + + drive = grub_strtoul (name + 2, 0, 10); + if (grub_errno != GRUB_ERR_NONE) + goto fail; + + if (name[0] == 'h') + drive += 0x80; + + return (int) drive ; + + fail: + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a biosdisk"); + return -1; +} + +static int +grub_biosdisk_call_hook (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, §or); + 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); + } +} |