| /* 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_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); |
| } |