From: Vladimir 'phcoder' Serbinenko Date: Thu, 7 Jul 2011 10:21:53 +0000 (+0200) Subject: merge mainline into lazy X-Git-Tag: 2.00~1165^2~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=00542307eb2cb27ca9618af10e46a3ad1ffbe91d;p=thirdparty%2Fgrub.git merge mainline into lazy --- 00542307eb2cb27ca9618af10e46a3ad1ffbe91d diff --cc grub-core/Makefile.core.def index 4146e3841,7bceaa98f..73d462185 --- a/grub-core/Makefile.core.def +++ b/grub-core/Makefile.core.def @@@ -162,10 -193,9 +193,10 @@@ kernel = emu = disk/host.c; emu = gnulib/progname.c; emu = gnulib/error.c; - emu = kern/emu/cache.S; + emu = kern/emu/cache_s.S; emu = kern/emu/console.c; emu = kern/emu/getroot.c; + emu = kern/emu/raid.c; emu = kern/emu/hostdisk.c; emu = kern/emu/hostfs.c; emu = kern/emu/main.c; diff --cc grub-core/disk/ahci.c index 000000000,415004fcf..38c4d5e2d mode 000000,100644..100644 --- a/grub-core/disk/ahci.c +++ b/grub-core/disk/ahci.c @@@ -1,0 -1,734 +1,738 @@@ + /* + * 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 . + */ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + GRUB_MOD_LICENSE ("GPLv3+"); + + struct grub_ahci_cmd_head + { + grub_uint32_t config; + grub_uint32_t transfered; + 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_FRE = 0x10, + GRUB_AHCI_HBA_PORT_CMD_CR = 0x8000, + GRUB_AHCI_HBA_PORT_CMD_FR = 0x4000, + }; + + 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; + 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; + }; + + static grub_err_t + grub_ahci_readwrite_real (struct grub_ahci_device *dev, + struct grub_disk_ata_pass_through_parms *parms, + int spinup); + + + 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 + init_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 int NESTED_FUNC_ATTR + grub_ahci_pciinit (grub_pci_device_t dev, + grub_pci_id_t pciid __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; + + hba = grub_pci_device_map_range (dev, bar & GRUB_PCI_ADDR_MEM_MASK, + sizeof (hba)); + + 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"); + + 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"); + + 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_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; + } + } + + 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; + } + */ + + nports = (hba->cap & GRUB_AHCI_HBA_CAP_NPORTS_MASK) + 1; + + grub_dprintf ("ahci", "%d AHCI ports\n", nports); + + for (i = 0; i < nports; i++) + { + struct grub_ahci_device *adev; + grub_uint32_t st; + + if (!(hba->ports_implemented & (1 << i))) + continue; + + grub_dprintf ("ahci", "status %d:%x\n", i, hba->ports[i].status); + /* FIXME: support hotplugging. */ + st = hba->ports[i].status; + if ((st & 0xf) != 0x3 && (st & 0xf) != 0x1) + continue; + + adev = grub_malloc (sizeof (*adev)); + if (!adev) + return 1; + + adev->hba = hba; + adev->port = i; + adev->present = 1; + adev->num = numdevs++; + + if (init_port (adev)) + { + grub_free (adev); + return 1; + } + + grub_list_push (GRUB_AS_LIST_P (&grub_ahci_devices), + GRUB_AS_LIST (adev)); + } + + return 0; + } + + static grub_err_t + grub_ahci_initialize (void) + { + grub_pci_iterate (grub_ahci_pciinit); + 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 grub_err_t + grub_ahci_restore_hw (void) + { + struct grub_ahci_device **pdev; + + for (pdev = &grub_ahci_devices; *pdev; pdev = &((*pdev)->next)) + if (init_port (*pdev)) + { + struct grub_ahci_device *odev; + odev = *pdev; + *pdev = (*pdev)->next; + grub_free (odev); + } + return GRUB_ERR_NONE; + } + + + + + static int -grub_ahci_iterate (int (*hook) (int id, int bus)) ++grub_ahci_iterate (int (*hook) (int id, int bus), ++ 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)) + 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_readwrite_real (struct grub_ahci_device *dev, + struct grub_disk_ata_pass_through_parms *parms, + int spinup) + { + 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 ((dev->hba->ports[dev->port].task_file_data & 0x80)) + { + 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; + } + dev->hba->ports[dev->port].command |= GRUB_AHCI_HBA_PORT_CMD_ST; + endtime = grub_get_time_ms () + (spinup ? 10000 : 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 start CR\n"); + break; + } + } + + 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_BAD_ARGUMENT, "incorrect ATAPI command size"); + + if (parms->size > GRUB_AHCI_PRDT_MAX_CHUNK_LENGTH) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "too big data buffer"); + + bufc = grub_memalign_dma32 (1024, parms->size + (parms->size & 1)); + + dev->hba->ports[dev->port].command |= 8; + + grub_dprintf ("ahci", "AHCI tfd = %x\n", + dev->hba->ports[dev->port].task_file_data); + /* 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) + | (1 << 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); + dev->command_list[0].transfered = 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])); + if (parms->cmdsize) + grub_memcpy ((char *) dev->command_table[0].command, parms->cmd, + parms->cmdsize); + + 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 + (parms->size & 1) - 1) + | GRUB_AHCI_INTERRUPT_ON_COMPLETE; + + 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, + (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 schedulded\n"); + grub_dprintf ("ahci", "AHCI tfd = %x\n", + dev->hba->ports[dev->port].task_file_data); + 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 tfd = %x\n", + dev->hba->ports[dev->port].task_file_data); + 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 ? 10000 : 1000); + while ((dev->hba->ports[dev->port].command_issue & 1)) + if (grub_get_time_ms () > endtime) + { + grub_dprintf ("ahci", "AHCI status <%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); + err = grub_error (GRUB_ERR_IO, "AHCI transfer timeouted"); + 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].transfered, + 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); + } + + 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->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 void *fini_hnd; + + GRUB_MOD_INIT(ahci) + { + /* To prevent two drivers operating on the same disks. */ + grub_disk_firmware_is_tainted = 1; + if (grub_disk_firmware_fini) + { + grub_disk_firmware_fini (); + grub_disk_firmware_fini = NULL; + } + + /* 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 --cc grub-core/disk/ata.c index 3a43a8cae,a8ccc2c59..9a09ee158 --- a/grub-core/disk/ata.c +++ b/grub-core/disk/ata.c @@@ -685,52 -358,84 +358,86 @@@ grub_ata_real_open (int id, int bus 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) + { + grub_free (ata); + return NULL; + } + return ata; + } + grub_free (ata); + grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device"); + return NULL; + } - if (dev->atapi) - continue; - - grub_snprintf (devname, sizeof (devname), - "ata%d", dev->port * 2 + dev->device); + static int -grub_ata_iterate (int (*hook_in) (const char *name)) ++grub_ata_iterate (int (*hook_in) (const char *name), ++ grub_disk_pull_t pull) + { + auto int hook (int id, int bus); + int hook (int id, int bus) + { + struct grub_ata *ata; + int ret; + char devname[40]; - if (hook (devname)) - return 1; - } + 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 = hook_in (devname); + grub_ata_real_close (ata); + return ret; + } + + grub_ata_dev_t p; + + for (p = grub_ata_dev_list; p; p = p->next) - if (p->iterate && p->iterate (hook)) ++ if (p->iterate && p->iterate (hook, pull)) + return 1; return 0; } static grub_err_t -grub_ata_open (const char *name, grub_disk_t disk) +grub_ata_open (const char *name, grub_disk_t disk, + grub_disk_pull_t pull __attribute__ ((unused))) { - struct grub_ata_device *dev; - grub_err_t err; - - for (dev = grub_ata_devices; dev; dev = dev->next) - { - char devname[10]; - grub_snprintf (devname, sizeof (devname), - "ata%d", dev->port * 2 + dev->device); - if (grub_strcmp (name, devname) == 0) - break; - } + unsigned id, bus; + struct grub_ata *ata; - if (! dev) - return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device"); - - if (dev->atapi) + 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; - err = check_device (dev); - - if (err) - return err; + if (ata->atapi) + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk"); - disk->total_sectors = dev->size; + disk->total_sectors = ata->size; - disk->id = (unsigned long) dev; + disk->id = grub_make_scsi_id (id, bus, 0); - disk->data = dev; + disk->data = ata; return 0; } @@@ -852,36 -523,62 +525,63 @@@ grub_atapi_write (struct grub_scsi *scs } static grub_err_t - grub_atapi_open (int devnum, struct grub_scsi *scsi) + grub_atapi_open (int id, int bus, struct grub_scsi *scsi) { - struct grub_ata_device *dev; - struct grub_ata_device *devfnd = 0; - grub_err_t err; + struct grub_ata *ata; - for (dev = grub_ata_devices; dev; dev = dev->next) - { - if (dev->port * 2 + dev->device == devnum) - { - devfnd = dev; - break; - } - } + 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"); - grub_dprintf ("ata", "opening ATAPI dev `ata%d'\n", devnum); + scsi->data = ata; + scsi->luns = 1; - if (! devfnd) - return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device"); + return GRUB_ERR_NONE; + } - err = check_device (devfnd); - if (err) - return err; + static int -grub_atapi_iterate (int NESTED_FUNC_ATTR (*hook_in) (int id, int bus, int luns)) ++grub_atapi_iterate (int NESTED_FUNC_ATTR (*hook_in) (int id, int bus, int luns), ++ grub_disk_pull_t pull) + { + auto int hook (int id, int bus); + int hook (int id, int bus) + { + struct grub_ata *ata; + int ret; - if (! devfnd->atapi) - return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device"); + ata = grub_ata_real_open (id, bus); - scsi->data = devfnd; + if (!ata) + { + grub_errno = GRUB_ERR_NONE; + return 0; + } + if (!ata->atapi) + { + grub_ata_real_close (ata); + return 0; + } + ret = hook_in (id, bus, 1); + grub_ata_real_close (ata); + return ret; + } + + grub_ata_dev_t p; + + for (p = grub_ata_dev_list; p; p = p->next) - if (p->iterate && p->iterate (hook)) ++ if (p->iterate && p->iterate (hook, pull)) + return 1; + return 0; + } - return GRUB_ERR_NONE; + static void + grub_atapi_close (grub_scsi_t disk) + { + struct grub_ata *ata = disk->data; + grub_ata_real_close (ata); } diff --cc grub-core/disk/efi/efidisk.c index 08094fa5c,07f0f6859..4ae1d7bb9 --- a/grub-core/disk/efi/efidisk.c +++ b/grub-core/disk/efi/efidisk.c @@@ -432,34 -378,34 +378,43 @@@ enumerate_disks (void } static int --grub_efidisk_iterate (int (*hook) (const char *name)) ++grub_efidisk_iterate (int (*hook) (const char *name), ++ grub_disk_pull_t pull) { struct grub_efidisk_data *d; char buf[16]; int count; -- for (d = fd_devices, count = 0; d; d = d->next, count++) ++ switch (pull) { -- grub_snprintf (buf, sizeof (buf), "fd%d", count); -- grub_dprintf ("efidisk", "iterating %s\n", buf); -- if (hook (buf)) -- return 1; -- } -- -- for (d = hd_devices, count = 0; d; d = d->next, count++) -- { -- grub_snprintf (buf, sizeof (buf), "hd%d", count); -- grub_dprintf ("efidisk", "iterating %s\n", buf); -- if (hook (buf)) -- return 1; -- } ++ 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)) ++ 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)) ++ return 1; ++ } -- for (d = cd_devices, count = 0; d; d = d->next, count++) -- { -- grub_snprintf (buf, sizeof (buf), "cd%d", count); -- grub_dprintf ("efidisk", "iterating %s\n", buf); -- if (hook (buf)) -- return 1; ++ for (d = cd_devices, count = 0; d; d = d->next, count++) ++ { ++ grub_snprintf (buf, sizeof (buf), "cd%d", count); ++ grub_dprintf ("efidisk", "iterating %s\n", buf); ++ if (hook (buf)) ++ return 1; ++ } ++ break; ++ default: ++ return 0; } return 0; @@@ -499,7 -445,7 +454,8 @@@ get_device (struct grub_efidisk_data *d } static grub_err_t --grub_efidisk_open (const char *name, struct grub_disk *disk) ++grub_efidisk_open (const char *name, struct grub_disk *disk, ++ grub_disk_pull_t pull __attribute__ ((unused))) { int num; struct grub_efidisk_data *d = 0; @@@ -785,7 -732,9 +742,11 @@@ grub_efidisk_get_device_name (grub_efi_ dup_ldp->length[0] = sizeof (*dup_ldp); dup_ldp->length[1] = 0; - grub_efidisk_iterate (find_parent_disk); + sdp = dup_dp; + - grub_efidisk_iterate (find_parent_disk); ++ grub_efidisk_iterate (find_parent_disk, GRUB_DISK_PULL_NONE); ++ if (!parent) ++ grub_efidisk_iterate (find_parent_disk, GRUB_DISK_PULL_REMOVABLE); grub_free (dup_dp); if (! parent) @@@ -813,36 -770,15 +782,17 @@@ else { /* This should be an entire disk. */ - auto int find_disk (const char *name); char *device_name = 0; - int find_disk (const char *name) - { - grub_disk_t disk; + sdp = dp; - disk = grub_disk_open (name); - if (! disk) - return 1; - - if (disk->dev->id == GRUB_DISK_DEVICE_EFIDISK_ID) - { - struct grub_efidisk_data *d; - - d = disk->data; - if (compare_device_paths (d->device_path, dp) == 0) - { - device_name = grub_strdup (disk->name); - grub_disk_close (disk); - return 1; - } - } - - grub_disk_close (disk); - return 0; - - } - - grub_efidisk_iterate (find_disk); - grub_efidisk_iterate (find_parent_disk); ++ grub_efidisk_iterate (find_parent_disk, GRUB_DISK_PULL_NONE); ++ if (!parent) ++ grub_efidisk_iterate (find_parent_disk, GRUB_DISK_PULL_REMOVABLE); + if (!parent) + return NULL; + device_name = grub_strdup (parent->name); + grub_disk_close (parent); return device_name; } diff --cc grub-core/disk/ieee1275/nand.c index e9450167e,e9450167e..4be5b5641 --- a/grub-core/disk/ieee1275/nand.c +++ b/grub-core/disk/ieee1275/nand.c @@@ -32,7 -32,7 +32,8 @@@ struct grub_nand_dat }; static int --grub_nand_iterate (int (*hook) (const char *name)) ++grub_nand_iterate (int (*hook) (const char *name), ++ grub_disk_pull_t pull) { auto int dev_iterate (struct grub_ieee1275_devalias *alias); int dev_iterate (struct grub_ieee1275_devalias *alias) @@@ -46,6 -46,6 +47,9 @@@ return 0; } ++ if (pull != GRUB_DISK_PULL_NONE) ++ return 0; ++ return grub_devalias_iterate (dev_iterate); } @@@ -54,7 -54,7 +58,8 @@@ grub_nand_read (grub_disk_t disk, grub_ grub_size_t size, char *buf); static grub_err_t --grub_nand_open (const char *name, grub_disk_t disk) ++grub_nand_open (const char *name, grub_disk_t disk, ++ grub_disk_pull_t pull __attribute__ ((unused))) { grub_ieee1275_ihandle_t dev_ihandle = 0; struct grub_nand_data *data = 0; diff --cc grub-core/disk/ieee1275/ofdisk.c index 0a935d5c2,7868b14b1..7b7db3a08 --- a/grub-core/disk/ieee1275/ofdisk.c +++ b/grub-core/disk/ieee1275/ofdisk.c @@@ -152,9 -152,9 +152,14 @@@ scan (void } static int --grub_ofdisk_iterate (int (*hook) (const char *name)) ++grub_ofdisk_iterate (int (*hook) (const char *name), ++ 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++) @@@ -228,7 -228,7 +233,8 @@@ compute_dev_path (const char *name } static grub_err_t --grub_ofdisk_open (const char *name, grub_disk_t disk) ++grub_ofdisk_open (const char *name, grub_disk_t disk, ++ grub_disk_pull_t pull __attribute__ ((unused))) { grub_ieee1275_phandle_t dev; char *devpath; diff --cc grub-core/disk/pata.c index 000000000,95828e8f6..ff6f77366 mode 000000,100644..100644 --- a/grub-core/disk/pata.c +++ b/grub-core/disk/pata.c @@@ -1,0 -1,530 +1,534 @@@ + /* ata_pthru.c - ATA pass through for ata.mod. */ + /* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + + #include + #include + #include + #include + #include + #ifndef GRUB_MACHINE_MIPS_QEMU_MIPS + #include + #include + #else + #define GRUB_MACHINE_PCI_IO_BASE 0xb4000000 + #endif + #include + + 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); + } + + static void + grub_pata_pio_read (struct grub_pata_device *dev, char *buf, grub_size_t size) + { + grub_uint16_t *buf16 = (grub_uint16_t *) buf; + unsigned int i; + + /* Read in the data, word by word. */ + for (i = 0; i < size / 2; i++) + buf16[i] = grub_le_to_cpu16 (grub_inw(dev->ioaddress + GRUB_ATA_REG_DATA)); + if (size & 1) + buf[size - 1] = (char) grub_le_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) + { + grub_uint16_t *buf16 = (grub_uint16_t *) buf; + unsigned int i; + + /* Write the data, word by word. */ + for (i = 0; i < size / 2; i++) + grub_outw(grub_cpu_to_le16 (buf16[i]), dev->ioaddress + GRUB_ATA_REG_DATA); + } + + /* 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); + + /* 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) + && (!parms->cmdsize + || ((grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON) + & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_DATA_IN))) + { + unsigned cnt; + + /* Wait for !BSY. */ + if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA)) + return grub_errno; + + if (parms->cmdsize) + { + 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_print_error (); + + return 0; + } + + #ifndef GRUB_MACHINE_MIPS_QEMU_MIPS + static int NESTED_FUNC_ATTR + grub_pata_pciinit (grub_pci_device_t dev, + grub_pci_id_t pciid) + { + static int compat_use[2] = { 0 }; + grub_pci_address_t addr; + grub_uint32_t class; + grub_uint32_t bar1; + grub_uint32_t bar2; + int rega; + int 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)) + { + rega = bar1 & ~3; + } + } + + 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); + 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->present = &devfnd->present; + + return GRUB_ERR_NONE; + } + + static int -grub_pata_iterate (int (*hook) (int id, int bus)) ++grub_pata_iterate (int (*hook) (int id, int bus), ++ 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)) + 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) + { + /* To prevent two drivers operating on the same disks. */ + grub_disk_firmware_is_tainted = 1; + if (grub_disk_firmware_fini) + { + grub_disk_firmware_fini (); + grub_disk_firmware_fini = NULL; + } + + /* ATA initialization. */ + grub_pata_initialize (); + + grub_ata_dev_register (&grub_pata_dev); + } + + GRUB_MOD_FINI(ata_pthru) + { + grub_ata_dev_unregister (&grub_pata_dev); + } diff --cc grub-core/disk/scsi.c index e85036860,902564164..5ab5177e9 --- a/grub-core/disk/scsi.c +++ b/grub-core/disk/scsi.c @@@ -357,11 -362,8 +363,8 @@@ grub_scsi_iterate (int (*hook) (const c return 0; } - if (pull != GRUB_DISK_PULL_NONE) - return 0; - for (p = grub_scsi_dev_list; p; p = p->next) -- if (p->iterate && (p->iterate) (scsi_iterate)) ++ if (p->iterate && (p->iterate) (scsi_iterate, pull)) return 1; return 0; diff --cc grub-core/disk/usbms.c index 2f1dbd487,1666197b1..a4c2addf1 --- a/grub-core/disk/usbms.c +++ b/grub-core/disk/usbms.c @@@ -211,10 -211,10 +211,14 @@@ grub_usbms_attach (grub_usb_device_t us static int - grub_usbms_iterate (int (*hook) (int bus, int luns)) -grub_usbms_iterate (int NESTED_FUNC_ATTR (*hook) (int id, int bus, int luns)) ++grub_usbms_iterate (int NESTED_FUNC_ATTR (*hook) (int id, int bus, int luns), ++ grub_disk_pull_t pull) { unsigned i; ++ if (pull != GRUB_DISK_PULL_NONE) ++ return 0; ++ grub_usb_poll_devices (); for (i = 0; i < ARRAY_SIZE (grub_usbms_devices); i++) diff --cc include/grub/ata.h index 9e3aaf0e6,84d5e2d10..6938b6a42 --- a/include/grub/ata.h +++ b/include/grub/ata.h @@@ -22,6 -22,6 +22,7 @@@ #include #include ++#include /* XXX: For now this only works on i386. */ #include @@@ -128,47 -178,41 +179,42 @@@ struct grub_at /* Set to 0 for ATA, set to 1 for ATAPI. */ int atapi; - struct grub_ata_device *next; + int dma; + + int *present; + + void *data; + + struct grub_ata_dev *dev; }; - grub_err_t EXPORT_FUNC(grub_ata_wait_not_busy) (struct grub_ata_device *dev, - int milliseconds); - grub_err_t EXPORT_FUNC(grub_ata_wait_drq) (struct grub_ata_device *dev, - int rw, int milliseconds); - void EXPORT_FUNC(grub_ata_pio_read) (struct grub_ata_device *dev, - char *buf, grub_size_t size); + typedef struct grub_ata *grub_ata_t; - static inline void - grub_ata_regset (struct grub_ata_device *dev, int reg, int val) + struct grub_ata_dev { - grub_outb (val, dev->ioaddress + reg); - } + /* Call HOOK with each device name, until HOOK returns non-zero. */ - int (*iterate) (int (*hook) (int id, int bus)); ++ int (*iterate) (int (*hook) (int id, int bus), ++ grub_disk_pull_t pull); - static inline grub_uint8_t - grub_ata_regget (struct grub_ata_device *dev, int reg) - { - return grub_inb (dev->ioaddress + reg); - } + /* Open the device named NAME, and set up SCSI. */ + grub_err_t (*open) (int id, int bus, struct grub_ata *scsi); - static inline void - grub_ata_regset2 (struct grub_ata_device *dev, int reg, int val) - { - grub_outb (val, dev->ioaddress2 + reg); - } + /* Close the scsi device SCSI. */ + void (*close) (struct grub_ata *ata); - static inline grub_uint8_t - grub_ata_regget2 (struct grub_ata_device *dev, int reg) - { - return grub_inb (dev->ioaddress2 + reg); - } + /* Read SIZE bytes from the device SCSI into BUF after sending the + command CMD of size CMDSIZE. */ + grub_err_t (*readwrite) (struct grub_ata *ata, + struct grub_disk_ata_pass_through_parms *parms, + int spinup); - static inline grub_err_t - grub_ata_check_ready (struct grub_ata_device *dev) - { - if (grub_ata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY) - return grub_ata_wait_not_busy (dev, GRUB_ATA_TOUT_STD); + /* The next scsi device. */ + struct grub_ata_dev *next; + }; + + typedef struct grub_ata_dev *grub_ata_dev_t; - return GRUB_ERR_NONE; - } + void grub_ata_dev_register (grub_ata_dev_t dev); + void grub_ata_dev_unregister (grub_ata_dev_t dev); #endif /* ! GRUB_ATA_HEADER */ diff --cc include/grub/scsi.h index b30d317c7,f71ef099d..dfee69c62 --- a/include/grub/scsi.h +++ b/include/grub/scsi.h @@@ -19,6 -19,6 +19,8 @@@ #ifndef GRUB_SCSI_H #define GRUB_SCSI_H 1 ++#include ++ typedef struct grub_scsi_dev *grub_scsi_dev_t; void grub_scsi_dev_register (grub_scsi_dev_t dev); @@@ -45,16 -49,11 +51,12 @@@ grub_make_scsi_id (int subsystem, int b struct grub_scsi_dev { - /* The device name. */ - const char *name; - - grub_uint8_t id; - /* Call HOOK with each device name, until HOOK returns non-zero. */ - int (*iterate) (int (*hook) (int bus, int luns)); - int (*iterate) (int NESTED_FUNC_ATTR (*hook) (int id, int bus, int luns)); ++ int (*iterate) (int NESTED_FUNC_ATTR (*hook) (int id, int bus, int luns), ++ grub_disk_pull_t pull); /* Open the device named NAME, and set up SCSI. */ - grub_err_t (*open) (int bus, struct grub_scsi *scsi); + grub_err_t (*open) (int id, int bus, struct grub_scsi *scsi); /* Close the scsi device SCSI. */ void (*close) (struct grub_scsi *scsi);