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