2N/A/* ata_pthru.c - ATA pass through for ata.mod. */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2009 Free Software Foundation, Inc.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/ata.h>
2N/A#include <grub/scsi.h>
2N/A#include <grub/disk.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/mm.h>
2N/A#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
2N/A#include <grub/pci.h>
2N/A#include <grub/cs5536.h>
2N/A#else
2N/A#define GRUB_MACHINE_PCI_IO_BASE 0xb4000000
2N/A#endif
2N/A#include <grub/time.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A/* At the moment, only two IDE ports are supported. */
2N/Astatic const grub_port_t grub_pata_ioaddress[] = { GRUB_ATA_CH0_PORT1,
2N/A GRUB_ATA_CH1_PORT1 };
2N/A
2N/Astruct grub_pata_device
2N/A{
2N/A /* IDE port to use. */
2N/A int port;
2N/A
2N/A /* IO addresses on which the registers for this device can be
2N/A found. */
2N/A grub_port_t ioaddress;
2N/A
2N/A /* Two devices can be connected to a single cable. Use this field
2N/A to select device 0 (commonly known as "master") or device 1
2N/A (commonly known as "slave"). */
2N/A int device;
2N/A
2N/A int present;
2N/A
2N/A struct grub_pata_device *next;
2N/A};
2N/A
2N/Astatic struct grub_pata_device *grub_pata_devices;
2N/A
2N/Astatic inline void
2N/Agrub_pata_regset (struct grub_pata_device *dev, int reg, int val)
2N/A{
2N/A grub_outb (val, dev->ioaddress + reg);
2N/A}
2N/A
2N/Astatic inline grub_uint8_t
2N/Agrub_pata_regget (struct grub_pata_device *dev, int reg)
2N/A{
2N/A return grub_inb (dev->ioaddress + reg);
2N/A}
2N/A
2N/A/* Wait for !BSY. */
2N/Astatic grub_err_t
2N/Agrub_pata_wait_not_busy (struct grub_pata_device *dev, int milliseconds)
2N/A{
2N/A /* ATA requires 400ns (after a write to CMD register) or
2N/A 1 PIO cycle (after a DRQ block transfer) before
2N/A first check of BSY. */
2N/A grub_millisleep (1);
2N/A
2N/A int i = 1;
2N/A grub_uint8_t sts;
2N/A while ((sts = grub_pata_regget (dev, GRUB_ATA_REG_STATUS))
2N/A & GRUB_ATA_STATUS_BUSY)
2N/A {
2N/A if (i >= milliseconds)
2N/A {
2N/A grub_dprintf ("pata", "timeout: %dms, status=0x%x\n",
2N/A milliseconds, sts);
2N/A return grub_error (GRUB_ERR_TIMEOUT, "PATA timeout");
2N/A }
2N/A
2N/A grub_millisleep (1);
2N/A i++;
2N/A }
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic inline grub_err_t
2N/Agrub_pata_check_ready (struct grub_pata_device *dev, int spinup)
2N/A{
2N/A if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS) & GRUB_ATA_STATUS_BUSY)
2N/A return grub_pata_wait_not_busy (dev, spinup ? GRUB_ATA_TOUT_SPINUP
2N/A : GRUB_ATA_TOUT_STD);
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic inline void
2N/Agrub_pata_wait (void)
2N/A{
2N/A grub_millisleep (50);
2N/A}
2N/A
2N/Astatic void
2N/Agrub_pata_pio_read (struct grub_pata_device *dev, char *buf, grub_size_t size)
2N/A{
2N/A unsigned int i;
2N/A
2N/A /* Read in the data, word by word. */
2N/A for (i = 0; i < size / 2; i++)
2N/A grub_set_unaligned16 (buf + 2 * i,
2N/A grub_le_to_cpu16 (grub_inw(dev->ioaddress
2N/A + GRUB_ATA_REG_DATA)));
2N/A if (size & 1)
2N/A buf[size - 1] = (char) grub_le_to_cpu16 (grub_inw (dev->ioaddress
2N/A + GRUB_ATA_REG_DATA));
2N/A}
2N/A
2N/Astatic void
2N/Agrub_pata_pio_write (struct grub_pata_device *dev, char *buf, grub_size_t size)
2N/A{
2N/A unsigned int i;
2N/A
2N/A /* Write the data, word by word. */
2N/A for (i = 0; i < size / 2; i++)
2N/A grub_outw(grub_cpu_to_le16 (grub_get_unaligned16 (buf + 2 * i)), dev->ioaddress + GRUB_ATA_REG_DATA);
2N/A}
2N/A
2N/A/* ATA pass through support, used by hdparm.mod. */
2N/Astatic grub_err_t
2N/Agrub_pata_readwrite (struct grub_ata *disk,
2N/A struct grub_disk_ata_pass_through_parms *parms,
2N/A int spinup)
2N/A{
2N/A struct grub_pata_device *dev = (struct grub_pata_device *) disk->data;
2N/A grub_size_t nread = 0;
2N/A int i;
2N/A
2N/A if (! (parms->cmdsize == 0 || parms->cmdsize == 12))
2N/A return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
2N/A "ATAPI non-12 byte commands not supported");
2N/A
2N/A grub_dprintf ("pata", "pata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
2N/A parms->taskfile.cmd,
2N/A parms->taskfile.features,
2N/A parms->taskfile.sectors);
2N/A grub_dprintf ("pata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%"
2N/A PRIuGRUB_SIZE "\n",
2N/A parms->taskfile.lba_high,
2N/A parms->taskfile.lba_mid,
2N/A parms->taskfile.lba_low, parms->size);
2N/A
2N/A /* Set registers. */
2N/A grub_pata_regset (dev, GRUB_ATA_REG_DISK, (dev->device << 4)
2N/A | (parms->taskfile.disk & 0xef));
2N/A if (grub_pata_check_ready (dev, spinup))
2N/A return grub_errno;
2N/A
2N/A for (i = GRUB_ATA_REG_SECTORS; i <= GRUB_ATA_REG_LBAHIGH; i++)
2N/A grub_pata_regset (dev, i,
2N/A parms->taskfile.raw[7 + (i - GRUB_ATA_REG_SECTORS)]);
2N/A for (i = GRUB_ATA_REG_FEATURES; i <= GRUB_ATA_REG_LBAHIGH; i++)
2N/A grub_pata_regset (dev, i, parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES]);
2N/A
2N/A /* Start command. */
2N/A grub_pata_regset (dev, GRUB_ATA_REG_CMD, parms->taskfile.cmd);
2N/A
2N/A /* Wait for !BSY. */
2N/A if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
2N/A return grub_errno;
2N/A
2N/A /* Check status. */
2N/A grub_int8_t sts = grub_pata_regget (dev, GRUB_ATA_REG_STATUS);
2N/A grub_dprintf ("pata", "status=0x%x\n", sts);
2N/A
2N/A if (parms->cmdsize)
2N/A {
2N/A grub_uint8_t irs;
2N/A /* Wait for !BSY. */
2N/A if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
2N/A return grub_errno;
2N/A
2N/A irs = grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON);
2N/A /* OK if DRQ is asserted and interrupt reason is as expected. */
2N/A if (!((sts & GRUB_ATA_STATUS_DRQ)
2N/A && (irs & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_CMD_OUT))
2N/A return grub_error (GRUB_ERR_READ_ERROR, "ATAPI protocol error");
2N/A /* Write the packet. */
2N/A grub_pata_pio_write (dev, parms->cmd, parms->cmdsize);
2N/A }
2N/A
2N/A /* Transfer data. */
2N/A while (nread < parms->size
2N/A && ((sts & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
2N/A == GRUB_ATA_STATUS_DRQ)
2N/A && (!parms->cmdsize
2N/A || ((grub_pata_regget (dev, GRUB_ATAPI_REG_IREASON)
2N/A & GRUB_ATAPI_IREASON_MASK) == GRUB_ATAPI_IREASON_DATA_IN)))
2N/A {
2N/A unsigned cnt;
2N/A
2N/A /* Wait for !BSY. */
2N/A if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
2N/A return grub_errno;
2N/A
2N/A if (parms->cmdsize)
2N/A {
2N/A cnt = grub_pata_regget (dev, GRUB_ATAPI_REG_CNTHIGH) << 8
2N/A | grub_pata_regget (dev, GRUB_ATAPI_REG_CNTLOW);
2N/A grub_dprintf("pata", "DRQ count=%u\n", cnt);
2N/A
2N/A /* Count of last transfer may be uneven. */
2N/A if (! (0 < cnt && cnt <= parms->size - nread
2N/A && (! (cnt & 1) || cnt == parms->size - nread)))
2N/A return grub_error (GRUB_ERR_READ_ERROR,
2N/A "invalid ATAPI transfer count");
2N/A }
2N/A else
2N/A cnt = GRUB_DISK_SECTOR_SIZE;
2N/A if (cnt > parms->size - nread)
2N/A cnt = parms->size - nread;
2N/A
2N/A if (parms->write)
2N/A grub_pata_pio_write (dev, (char *) parms->buffer + nread, cnt);
2N/A else
2N/A grub_pata_pio_read (dev, (char *) parms->buffer + nread, cnt);
2N/A
2N/A nread += cnt;
2N/A }
2N/A if (parms->write)
2N/A {
2N/A /* Check for write error. */
2N/A if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
2N/A return grub_errno;
2N/A
2N/A if (grub_pata_regget (dev, GRUB_ATA_REG_STATUS)
2N/A & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
2N/A return grub_error (GRUB_ERR_WRITE_ERROR, "ATA write error");
2N/A }
2N/A parms->size = nread;
2N/A
2N/A /* Wait for !BSY. */
2N/A if (grub_pata_wait_not_busy (dev, GRUB_ATA_TOUT_DATA))
2N/A return grub_errno;
2N/A
2N/A /* Return registers. */
2N/A for (i = GRUB_ATA_REG_ERROR; i <= GRUB_ATA_REG_STATUS; i++)
2N/A parms->taskfile.raw[i - GRUB_ATA_REG_FEATURES] = grub_pata_regget (dev, i);
2N/A
2N/A grub_dprintf ("pata", "status=0x%x, error=0x%x, sectors=0x%x\n",
2N/A parms->taskfile.status,
2N/A parms->taskfile.error,
2N/A parms->taskfile.sectors);
2N/A
2N/A if (parms->taskfile.status
2N/A & (GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR))
2N/A return grub_error (GRUB_ERR_READ_ERROR, "PATA passthrough failed");
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Acheck_device (struct grub_pata_device *dev)
2N/A{
2N/A grub_pata_regset (dev, GRUB_ATA_REG_DISK, dev->device << 4);
2N/A grub_pata_wait ();
2N/A
2N/A /* Try to detect if the port is in use by writing to it,
2N/A waiting for a while and reading it again. If the value
2N/A was preserved, there is a device connected. */
2N/A grub_pata_regset (dev, GRUB_ATA_REG_SECTORS, 0x5A);
2N/A grub_pata_wait ();
2N/A grub_uint8_t sec = grub_pata_regget (dev, GRUB_ATA_REG_SECTORS);
2N/A grub_dprintf ("ata", "sectors=0x%x\n", sec);
2N/A if (sec != 0x5A)
2N/A return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no device connected");
2N/A
2N/A /* The above test may detect a second (slave) device
2N/A connected to a SATA controller which supports only one
2N/A (master) device. It is not safe to use the status register
2N/A READY bit to check for controller channel existence. Some
2N/A ATAPI commands (RESET, DIAGNOSTIC) may clear this bit. */
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_pata_device_initialize (int port, int device, int addr)
2N/A{
2N/A struct grub_pata_device *dev;
2N/A struct grub_pata_device **devp;
2N/A grub_err_t err;
2N/A
2N/A grub_dprintf ("pata", "detecting device %d,%d (0x%x)\n",
2N/A port, device, addr);
2N/A
2N/A dev = grub_malloc (sizeof(*dev));
2N/A if (! dev)
2N/A return grub_errno;
2N/A
2N/A /* Setup the device information. */
2N/A dev->port = port;
2N/A dev->device = device;
2N/A dev->ioaddress = addr + GRUB_MACHINE_PCI_IO_BASE;
2N/A dev->present = 1;
2N/A dev->next = NULL;
2N/A
2N/A /* Register the device. */
2N/A for (devp = &grub_pata_devices; *devp; devp = &(*devp)->next);
2N/A *devp = dev;
2N/A
2N/A err = check_device (dev);
2N/A if (err)
2N/A grub_print_error ();
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
2N/Astatic int NESTED_FUNC_ATTR
2N/Agrub_pata_pciinit (grub_pci_device_t dev,
2N/A grub_pci_id_t pciid)
2N/A{
2N/A static int compat_use[2] = { 0 };
2N/A grub_pci_address_t addr;
2N/A grub_uint32_t class;
2N/A grub_uint32_t bar1;
2N/A grub_uint32_t bar2;
2N/A int rega;
2N/A int i;
2N/A static int controller = 0;
2N/A int cs5536 = 0;
2N/A int nports = 2;
2N/A
2N/A /* Read class. */
2N/A addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
2N/A class = grub_pci_read (addr);
2N/A
2N/A /* AMD CS5536 Southbridge. */
2N/A if (pciid == GRUB_CS5536_PCIID)
2N/A {
2N/A cs5536 = 1;
2N/A nports = 1;
2N/A }
2N/A
2N/A /* Check if this class ID matches that of a PCI IDE Controller. */
2N/A if (!cs5536 && (class >> 16 != 0x0101))
2N/A return 0;
2N/A
2N/A for (i = 0; i < nports; i++)
2N/A {
2N/A /* Set to 0 when the channel operated in compatibility mode. */
2N/A int compat;
2N/A
2N/A /* We don't support non-compatibility mode for CS5536. */
2N/A if (cs5536)
2N/A compat = 0;
2N/A else
2N/A compat = (class >> (8 + 2 * i)) & 1;
2N/A
2N/A rega = 0;
2N/A
2N/A /* If the channel is in compatibility mode, just assign the
2N/A default registers. */
2N/A if (compat == 0 && !compat_use[i])
2N/A {
2N/A rega = grub_pata_ioaddress[i];
2N/A compat_use[i] = 1;
2N/A }
2N/A else if (compat)
2N/A {
2N/A /* Read the BARs, which either contain a mmapped IO address
2N/A or the IO port address. */
2N/A addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
2N/A + sizeof (grub_uint64_t) * i);
2N/A bar1 = grub_pci_read (addr);
2N/A addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESSES
2N/A + sizeof (grub_uint64_t) * i
2N/A + sizeof (grub_uint32_t));
2N/A bar2 = grub_pci_read (addr);
2N/A
2N/A /* Check if the BARs describe an IO region. */
2N/A if ((bar1 & 1) && (bar2 & 1))
2N/A {
2N/A rega = bar1 & ~3;
2N/A }
2N/A }
2N/A
2N/A grub_dprintf ("pata",
2N/A "PCI dev (%d,%d,%d) compat=%d rega=0x%x\n",
2N/A grub_pci_get_bus (dev), grub_pci_get_device (dev),
2N/A grub_pci_get_function (dev), compat, rega);
2N/A
2N/A if (rega)
2N/A {
2N/A grub_errno = GRUB_ERR_NONE;
2N/A grub_pata_device_initialize (controller * 2 + i, 0, rega);
2N/A
2N/A /* Most errors raised by grub_ata_device_initialize() are harmless.
2N/A They just indicate this particular drive is not responding, most
2N/A likely because it doesn't exist. We might want to ignore specific
2N/A error types here, instead of printing them. */
2N/A if (grub_errno)
2N/A {
2N/A grub_print_error ();
2N/A grub_errno = GRUB_ERR_NONE;
2N/A }
2N/A
2N/A grub_pata_device_initialize (controller * 2 + i, 1, rega);
2N/A
2N/A /* Likewise. */
2N/A if (grub_errno)
2N/A {
2N/A grub_print_error ();
2N/A grub_errno = GRUB_ERR_NONE;
2N/A }
2N/A }
2N/A }
2N/A
2N/A controller++;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Agrub_pata_initialize (void)
2N/A{
2N/A grub_pci_iterate (grub_pata_pciinit);
2N/A return 0;
2N/A}
2N/A#else
2N/Astatic grub_err_t
2N/Agrub_pata_initialize (void)
2N/A{
2N/A int i;
2N/A for (i = 0; i < 2; i++)
2N/A {
2N/A grub_pata_device_initialize (i, 0, grub_pata_ioaddress[i]);
2N/A grub_pata_device_initialize (i, 1, grub_pata_ioaddress[i]);
2N/A }
2N/A return 0;
2N/A}
2N/A#endif
2N/A
2N/Astatic grub_err_t
2N/Agrub_pata_open (int id, int devnum, struct grub_ata *ata)
2N/A{
2N/A struct grub_pata_device *dev;
2N/A struct grub_pata_device *devfnd = 0;
2N/A grub_err_t err;
2N/A
2N/A if (id != GRUB_SCSI_SUBSYSTEM_PATA)
2N/A return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a PATA device");
2N/A
2N/A for (dev = grub_pata_devices; dev; dev = dev->next)
2N/A {
2N/A if (dev->port * 2 + dev->device == devnum)
2N/A {
2N/A devfnd = dev;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A grub_dprintf ("pata", "opening PATA dev `ata%d'\n", devnum);
2N/A
2N/A if (! devfnd)
2N/A return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such PATA device");
2N/A
2N/A err = check_device (devfnd);
2N/A if (err)
2N/A return err;
2N/A
2N/A ata->data = devfnd;
2N/A ata->dma = 0;
2N/A ata->present = &devfnd->present;
2N/A
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/Astatic int
2N/Agrub_pata_iterate (int (*hook) (int id, int bus),
2N/A grub_disk_pull_t pull)
2N/A{
2N/A struct grub_pata_device *dev;
2N/A
2N/A if (pull != GRUB_DISK_PULL_NONE)
2N/A return 0;
2N/A
2N/A for (dev = grub_pata_devices; dev; dev = dev->next)
2N/A if (hook (GRUB_SCSI_SUBSYSTEM_PATA, dev->port * 2 + dev->device))
2N/A return 1;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/A
2N/Astatic struct grub_ata_dev grub_pata_dev =
2N/A {
2N/A .iterate = grub_pata_iterate,
2N/A .open = grub_pata_open,
2N/A .readwrite = grub_pata_readwrite,
2N/A };
2N/A
2N/A
2N/A
2N/A
2N/AGRUB_MOD_INIT(ata_pthru)
2N/A{
2N/A /* To prevent two drivers operating on the same disks. */
2N/A grub_disk_firmware_is_tainted = 1;
2N/A if (grub_disk_firmware_fini)
2N/A {
2N/A grub_disk_firmware_fini ();
2N/A grub_disk_firmware_fini = NULL;
2N/A }
2N/A
2N/A /* ATA initialization. */
2N/A grub_pata_initialize ();
2N/A
2N/A grub_ata_dev_register (&grub_pata_dev);
2N/A}
2N/A
2N/AGRUB_MOD_FINI(ata_pthru)
2N/A{
2N/A grub_ata_dev_unregister (&grub_pata_dev);
2N/A}