vaseboot/VasEBoot-core/disk/pata.c

557 lines
16 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ata_pthru.c - ATA pass through for ata.mod. */
/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* VAS_EBOOT 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.
*
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
*/
#include <VasEBoot/ata.h>
#include <VasEBoot/scsi.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/dl.h>
#include <VasEBoot/mm.h>
#ifndef VAS_EBOOT_MACHINE_MIPS_QEMU_MIPS
#include <VasEBoot/pci.h>
#include <VasEBoot/cs5536.h>
#else
#define VAS_EBOOT_MACHINE_PCI_IO_BASE 0xb4000000
#endif
#include <VasEBoot/time.h>
VAS_EBOOT_MOD_LICENSE ("GPLv3+");
/* At the moment, only two IDE ports are supported. */
static const VasEBoot_port_t VasEBoot_pata_ioaddress[] = { VAS_EBOOT_ATA_CH0_PORT1,
VAS_EBOOT_ATA_CH1_PORT1 };
struct VasEBoot_pata_device
{
/* IDE port to use. */
int port;
/* IO addresses on which the registers for this device can be
found. */
VasEBoot_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 VasEBoot_pata_device *next;
};
static struct VasEBoot_pata_device *VasEBoot_pata_devices;
static inline void
VasEBoot_pata_regset (struct VasEBoot_pata_device *dev, int reg, int val)
{
VasEBoot_outb (val, dev->ioaddress + reg);
}
static inline VasEBoot_uint8_t
VasEBoot_pata_regget (struct VasEBoot_pata_device *dev, int reg)
{
return VasEBoot_inb (dev->ioaddress + reg);
}
/* Wait for !BSY. */
static VasEBoot_err_t
VasEBoot_pata_wait_not_busy (struct VasEBoot_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. */
VasEBoot_millisleep (1);
int i = 1;
VasEBoot_uint8_t sts;
while ((sts = VasEBoot_pata_regget (dev, VAS_EBOOT_ATA_REG_STATUS))
& VAS_EBOOT_ATA_STATUS_BUSY)
{
if (i >= milliseconds)
{
VasEBoot_dprintf ("pata", "timeout: %dms, status=0x%x\n",
milliseconds, sts);
return VasEBoot_error (VAS_EBOOT_ERR_TIMEOUT, "PATA timeout");
}
VasEBoot_millisleep (1);
i++;
}
return VAS_EBOOT_ERR_NONE;
}
static inline VasEBoot_err_t
VasEBoot_pata_check_ready (struct VasEBoot_pata_device *dev, int spinup)
{
if (VasEBoot_pata_regget (dev, VAS_EBOOT_ATA_REG_STATUS) & VAS_EBOOT_ATA_STATUS_BUSY)
return VasEBoot_pata_wait_not_busy (dev, spinup ? VAS_EBOOT_ATA_TOUT_SPINUP
: VAS_EBOOT_ATA_TOUT_STD);
return VAS_EBOOT_ERR_NONE;
}
static inline void
VasEBoot_pata_wait (void)
{
VasEBoot_millisleep (50);
}
#ifdef VAS_EBOOT_MACHINE_MIPS_QEMU_MIPS
#define VasEBoot_ata_to_cpu16(x) ((VasEBoot_uint16_t) (x))
#define VasEBoot_cpu_to_ata16(x) ((VasEBoot_uint16_t) (x))
#else
#define VasEBoot_ata_to_cpu16 VasEBoot_le_to_cpu16
#define VasEBoot_cpu_to_ata16 VasEBoot_cpu_to_le16
#endif
static void
VasEBoot_pata_pio_read (struct VasEBoot_pata_device *dev, char *buf, VasEBoot_size_t size)
{
unsigned int i;
/* Read in the data, word by word. */
for (i = 0; i < size / 2; i++)
VasEBoot_set_unaligned16 (buf + 2 * i,
VasEBoot_ata_to_cpu16 (VasEBoot_inw(dev->ioaddress
+ VAS_EBOOT_ATA_REG_DATA)));
if (size & 1)
buf[size - 1] = (char) VasEBoot_ata_to_cpu16 (VasEBoot_inw (dev->ioaddress
+ VAS_EBOOT_ATA_REG_DATA));
}
static void
VasEBoot_pata_pio_write (struct VasEBoot_pata_device *dev, char *buf, VasEBoot_size_t size)
{
unsigned int i;
/* Write the data, word by word. */
for (i = 0; i < size / 2; i++)
VasEBoot_outw(VasEBoot_cpu_to_ata16 (VasEBoot_get_unaligned16 (buf + 2 * i)), dev->ioaddress + VAS_EBOOT_ATA_REG_DATA);
}
/* ATA pass through support, used by hdparm.mod. */
static VasEBoot_err_t
VasEBoot_pata_readwrite (struct VasEBoot_ata *disk,
struct VasEBoot_disk_ata_pass_through_parms *parms,
int spinup)
{
struct VasEBoot_pata_device *dev = (struct VasEBoot_pata_device *) disk->data;
VasEBoot_size_t nread = 0;
int i;
if (! (parms->cmdsize == 0 || parms->cmdsize == 12))
return VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET,
"ATAPI non-12 byte commands not supported");
VasEBoot_dprintf ("pata", "pata_pass_through: cmd=0x%x, features=0x%x, sectors=0x%x\n",
parms->taskfile.cmd,
parms->taskfile.features,
parms->taskfile.sectors);
VasEBoot_dprintf ("pata", "lba_high=0x%x, lba_mid=0x%x, lba_low=0x%x, size=%"
PRIuVAS_EBOOT_SIZE "\n",
parms->taskfile.lba_high,
parms->taskfile.lba_mid,
parms->taskfile.lba_low, parms->size);
/* Set registers. */
VasEBoot_pata_regset (dev, VAS_EBOOT_ATA_REG_DISK, (dev->device << 4)
| (parms->taskfile.disk & 0xef));
if (VasEBoot_pata_check_ready (dev, spinup))
return VasEBoot_errno;
for (i = VAS_EBOOT_ATA_REG_SECTORS; i <= VAS_EBOOT_ATA_REG_LBAHIGH; i++)
VasEBoot_pata_regset (dev, i,
parms->taskfile.raw[7 + (i - VAS_EBOOT_ATA_REG_SECTORS)]);
for (i = VAS_EBOOT_ATA_REG_FEATURES; i <= VAS_EBOOT_ATA_REG_LBAHIGH; i++)
VasEBoot_pata_regset (dev, i, parms->taskfile.raw[i - VAS_EBOOT_ATA_REG_FEATURES]);
/* Start command. */
VasEBoot_pata_regset (dev, VAS_EBOOT_ATA_REG_CMD, parms->taskfile.cmd);
/* Wait for !BSY. */
if (VasEBoot_pata_wait_not_busy (dev, VAS_EBOOT_ATA_TOUT_DATA))
return VasEBoot_errno;
/* Check status. */
VasEBoot_int8_t sts = VasEBoot_pata_regget (dev, VAS_EBOOT_ATA_REG_STATUS);
VasEBoot_dprintf ("pata", "status=0x%x\n", sts);
if (parms->cmdsize)
{
VasEBoot_uint8_t irs;
/* Wait for !BSY. */
if (VasEBoot_pata_wait_not_busy (dev, VAS_EBOOT_ATA_TOUT_DATA))
return VasEBoot_errno;
irs = VasEBoot_pata_regget (dev, VAS_EBOOT_ATAPI_REG_IREASON);
/* OK if DRQ is asserted and interrupt reason is as expected. */
if (!((sts & VAS_EBOOT_ATA_STATUS_DRQ)
&& (irs & VAS_EBOOT_ATAPI_IREASON_MASK) == VAS_EBOOT_ATAPI_IREASON_CMD_OUT))
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, "ATAPI protocol error");
/* Write the packet. */
VasEBoot_pata_pio_write (dev, parms->cmd, parms->cmdsize);
}
/* Transfer data. */
while (nread < parms->size
&& (sts & (VAS_EBOOT_ATA_STATUS_DRQ | VAS_EBOOT_ATA_STATUS_ERR))
== VAS_EBOOT_ATA_STATUS_DRQ)
{
unsigned cnt;
/* Wait for !BSY. */
if (VasEBoot_pata_wait_not_busy (dev, VAS_EBOOT_ATA_TOUT_DATA))
return VasEBoot_errno;
if (parms->cmdsize)
{
if ((VasEBoot_pata_regget (dev, VAS_EBOOT_ATAPI_REG_IREASON)
& VAS_EBOOT_ATAPI_IREASON_MASK) != VAS_EBOOT_ATAPI_IREASON_DATA_IN)
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, "ATAPI protocol error");
cnt = VasEBoot_pata_regget (dev, VAS_EBOOT_ATAPI_REG_CNTHIGH) << 8
| VasEBoot_pata_regget (dev, VAS_EBOOT_ATAPI_REG_CNTLOW);
VasEBoot_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 VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR,
"invalid ATAPI transfer count");
}
else
cnt = VAS_EBOOT_DISK_SECTOR_SIZE;
if (cnt > parms->size - nread)
cnt = parms->size - nread;
if (parms->write)
VasEBoot_pata_pio_write (dev, (char *) parms->buffer + nread, cnt);
else
VasEBoot_pata_pio_read (dev, (char *) parms->buffer + nread, cnt);
nread += cnt;
}
if (parms->write)
{
/* Check for write error. */
if (VasEBoot_pata_wait_not_busy (dev, VAS_EBOOT_ATA_TOUT_DATA))
return VasEBoot_errno;
if (VasEBoot_pata_regget (dev, VAS_EBOOT_ATA_REG_STATUS)
& (VAS_EBOOT_ATA_STATUS_DRQ | VAS_EBOOT_ATA_STATUS_ERR))
return VasEBoot_error (VAS_EBOOT_ERR_WRITE_ERROR, "ATA write error");
}
parms->size = nread;
/* Wait for !BSY. */
if (VasEBoot_pata_wait_not_busy (dev, VAS_EBOOT_ATA_TOUT_DATA))
return VasEBoot_errno;
/* Return registers. */
for (i = VAS_EBOOT_ATA_REG_ERROR; i <= VAS_EBOOT_ATA_REG_STATUS; i++)
parms->taskfile.raw[i - VAS_EBOOT_ATA_REG_FEATURES] = VasEBoot_pata_regget (dev, i);
VasEBoot_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
& (VAS_EBOOT_ATA_STATUS_DRQ | VAS_EBOOT_ATA_STATUS_ERR))
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, "PATA passthrough failed");
return VAS_EBOOT_ERR_NONE;
}
static VasEBoot_err_t
check_device (struct VasEBoot_pata_device *dev)
{
VasEBoot_pata_regset (dev, VAS_EBOOT_ATA_REG_DISK, dev->device << 4);
VasEBoot_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. */
VasEBoot_pata_regset (dev, VAS_EBOOT_ATA_REG_SECTORS, 0x5A);
VasEBoot_pata_wait ();
VasEBoot_uint8_t sec = VasEBoot_pata_regget (dev, VAS_EBOOT_ATA_REG_SECTORS);
VasEBoot_dprintf ("ata", "sectors=0x%x\n", sec);
if (sec != 0x5A)
return VasEBoot_error (VAS_EBOOT_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 VAS_EBOOT_ERR_NONE;
}
static VasEBoot_err_t
VasEBoot_pata_device_initialize (int port, int device, int addr)
{
struct VasEBoot_pata_device *dev;
struct VasEBoot_pata_device **devp;
VasEBoot_err_t err;
VasEBoot_dprintf ("pata", "detecting device %d,%d (0x%x)\n",
port, device, addr);
dev = VasEBoot_malloc (sizeof(*dev));
if (! dev)
return VasEBoot_errno;
/* Setup the device information. */
dev->port = port;
dev->device = device;
dev->ioaddress = addr + VAS_EBOOT_MACHINE_PCI_IO_BASE;
dev->present = 1;
dev->next = NULL;
/* Register the device. */
for (devp = &VasEBoot_pata_devices; *devp; devp = &(*devp)->next);
*devp = dev;
err = check_device (dev);
if (err == VAS_EBOOT_ERR_UNKNOWN_DEVICE)
{
VasEBoot_dprintf ("pata", "%s\n", VasEBoot_errmsg);
VasEBoot_error_pop ();
}
if (err)
VasEBoot_print_error ();
return 0;
}
#ifndef VAS_EBOOT_MACHINE_MIPS_QEMU_MIPS
static int
VasEBoot_pata_pciinit (VasEBoot_pci_device_t dev,
VasEBoot_pci_id_t pciid,
void *data __attribute__ ((unused)))
{
static int compat_use[2] = { 0 };
VasEBoot_pci_address_t addr;
VasEBoot_uint32_t class;
VasEBoot_uint32_t bar1;
VasEBoot_uint32_t bar2;
int rega;
int i;
static int controller = 0;
int cs5536 = 0;
int nports = 2;
/* Read class. */
addr = VasEBoot_pci_make_address (dev, VAS_EBOOT_PCI_REG_CLASS);
class = VasEBoot_pci_read (addr);
/* AMD CS5536 Southbridge. */
if (pciid == VAS_EBOOT_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 = VasEBoot_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 = VasEBoot_pci_make_address (dev, VAS_EBOOT_PCI_REG_ADDRESSES
+ sizeof (VasEBoot_uint64_t) * i);
bar1 = VasEBoot_pci_read (addr);
addr = VasEBoot_pci_make_address (dev, VAS_EBOOT_PCI_REG_ADDRESSES
+ sizeof (VasEBoot_uint64_t) * i
+ sizeof (VasEBoot_uint32_t));
bar2 = VasEBoot_pci_read (addr);
/* Check if the BARs describe an IO region. */
if ((bar1 & 1) && (bar2 & 1) && (bar1 & ~3))
{
rega = bar1 & ~3;
addr = VasEBoot_pci_make_address (dev, VAS_EBOOT_PCI_REG_COMMAND);
VasEBoot_pci_write_word (addr, VasEBoot_pci_read_word (addr)
| VAS_EBOOT_PCI_COMMAND_IO_ENABLED
| VAS_EBOOT_PCI_COMMAND_MEM_ENABLED
| VAS_EBOOT_PCI_COMMAND_BUS_MASTER);
}
}
VasEBoot_dprintf ("pata",
"PCI dev (%d,%d,%d) compat=%d rega=0x%x\n",
VasEBoot_pci_get_bus (dev), VasEBoot_pci_get_device (dev),
VasEBoot_pci_get_function (dev), compat, rega);
if (rega)
{
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
VasEBoot_pata_device_initialize (controller * 2 + i, 0, rega);
/* Most errors raised by VasEBoot_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 (VasEBoot_errno)
{
VasEBoot_print_error ();
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
}
VasEBoot_pata_device_initialize (controller * 2 + i, 1, rega);
/* Likewise. */
if (VasEBoot_errno)
{
VasEBoot_print_error ();
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
}
}
}
controller++;
return 0;
}
static VasEBoot_err_t
VasEBoot_pata_initialize (void)
{
VasEBoot_pci_iterate (VasEBoot_pata_pciinit, NULL);
return 0;
}
#else
static VasEBoot_err_t
VasEBoot_pata_initialize (void)
{
int i;
for (i = 0; i < 2; i++)
{
VasEBoot_pata_device_initialize (i, 0, VasEBoot_pata_ioaddress[i]);
VasEBoot_pata_device_initialize (i, 1, VasEBoot_pata_ioaddress[i]);
}
return 0;
}
#endif
static VasEBoot_err_t
VasEBoot_pata_open (int id, int devnum, struct VasEBoot_ata *ata)
{
struct VasEBoot_pata_device *dev;
struct VasEBoot_pata_device *devfnd = 0;
VasEBoot_err_t err;
if (id != VAS_EBOOT_SCSI_SUBSYSTEM_PATA)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "not a PATA device");
for (dev = VasEBoot_pata_devices; dev; dev = dev->next)
{
if (dev->port * 2 + dev->device == devnum)
{
devfnd = dev;
break;
}
}
VasEBoot_dprintf ("pata", "opening PATA dev `ata%d'\n", devnum);
if (! devfnd)
return VasEBoot_error (VAS_EBOOT_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 VAS_EBOOT_ERR_NONE;
}
static int
VasEBoot_pata_iterate (VasEBoot_ata_dev_iterate_hook_t hook, void *hook_data,
VasEBoot_disk_pull_t pull)
{
struct VasEBoot_pata_device *dev;
if (pull != VAS_EBOOT_DISK_PULL_NONE)
return 0;
for (dev = VasEBoot_pata_devices; dev; dev = dev->next)
if (hook (VAS_EBOOT_SCSI_SUBSYSTEM_PATA, dev->port * 2 + dev->device,
hook_data))
return 1;
return 0;
}
static struct VasEBoot_ata_dev VasEBoot_pata_dev =
{
.iterate = VasEBoot_pata_iterate,
.open = VasEBoot_pata_open,
.readwrite = VasEBoot_pata_readwrite,
};
VAS_EBOOT_MOD_INIT(ata_pthru)
{
VasEBoot_stop_disk_firmware ();
/* ATA initialization. */
VasEBoot_pata_initialize ();
VasEBoot_ata_dev_register (&VasEBoot_pata_dev);
}
VAS_EBOOT_MOD_FINI(ata_pthru)
{
VasEBoot_ata_dev_unregister (&VasEBoot_pata_dev);
}