vaseboot/VasEBoot-core/disk/ata.c

683 lines
18 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.c - ATA disk access. */
/*
* VAS_EBOOT -- GRand Unified Bootloader
* Copyright (C) 2007, 2008, 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/dl.h>
#include <VasEBoot/disk.h>
#include <VasEBoot/mm.h>
#include <VasEBoot/scsi.h>
VAS_EBOOT_MOD_LICENSE ("GPLv3+");
static VasEBoot_ata_dev_t VasEBoot_ata_dev_list;
/* Byteorder has to be changed before strings can be read. */
static void
VasEBoot_ata_strncpy (VasEBoot_uint16_t *dst16, VasEBoot_uint16_t *src16, VasEBoot_size_t len)
{
unsigned int i;
for (i = 0; i < len / 2; i++)
*(dst16++) = VasEBoot_swap_bytes16 (*(src16++));
*dst16 = 0;
}
static void
VasEBoot_ata_dumpinfo (struct VasEBoot_ata *dev, VasEBoot_uint16_t *info)
{
VasEBoot_uint16_t text[21];
/* The device information was read, dump it for debugging. */
VasEBoot_ata_strncpy (text, info + 10, 20);
VasEBoot_dprintf ("ata", "Serial: %s\n", (char *) text);
VasEBoot_ata_strncpy (text, info + 23, 8);
VasEBoot_dprintf ("ata", "Firmware: %s\n", (char *) text);
VasEBoot_ata_strncpy (text, info + 27, 40);
VasEBoot_dprintf ("ata", "Model: %s\n", (char *) text);
if (! dev->atapi)
{
VasEBoot_dprintf ("ata", "Addressing: %d\n", dev->addr);
VasEBoot_dprintf ("ata", "Sectors: %lld\n", (unsigned long long) dev->size);
VasEBoot_dprintf ("ata", "Sector size: %u\n", 1U << dev->log_sector_size);
}
}
static VasEBoot_err_t
VasEBoot_atapi_identify (struct VasEBoot_ata *dev)
{
struct VasEBoot_disk_ata_pass_through_parms parms;
VasEBoot_uint16_t *info;
VasEBoot_err_t err;
info = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE);
if (! info)
return VasEBoot_errno;
VasEBoot_memset (&parms, 0, sizeof (parms));
parms.taskfile.disk = 0xE0;
parms.taskfile.cmd = VAS_EBOOT_ATA_CMD_IDENTIFY_PACKET_DEVICE;
parms.size = VAS_EBOOT_DISK_SECTOR_SIZE;
parms.buffer = info;
err = dev->dev->readwrite (dev, &parms, *dev->present);
if (err)
{
*dev->present = 0;
return err;
}
if (parms.size != VAS_EBOOT_DISK_SECTOR_SIZE)
{
*dev->present = 0;
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE,
"device cannot be identified");
}
dev->atapi = 1;
VasEBoot_ata_dumpinfo (dev, info);
VasEBoot_free (info);
return VAS_EBOOT_ERR_NONE;
}
static VasEBoot_err_t
VasEBoot_ata_identify (struct VasEBoot_ata *dev)
{
struct VasEBoot_disk_ata_pass_through_parms parms;
VasEBoot_uint64_t *info64;
VasEBoot_uint32_t *info32;
VasEBoot_uint16_t *info16;
VasEBoot_err_t err;
if (dev->atapi)
return VasEBoot_atapi_identify (dev);
info64 = VasEBoot_malloc (VAS_EBOOT_DISK_SECTOR_SIZE);
if (info64 == NULL)
return VasEBoot_errno;
info32 = (VasEBoot_uint32_t *) info64;
info16 = (VasEBoot_uint16_t *) info64;
VasEBoot_memset (&parms, 0, sizeof (parms));
parms.buffer = info16;
parms.size = VAS_EBOOT_DISK_SECTOR_SIZE;
parms.taskfile.disk = 0xE0;
parms.taskfile.cmd = VAS_EBOOT_ATA_CMD_IDENTIFY_DEVICE;
err = dev->dev->readwrite (dev, &parms, *dev->present);
if (err || parms.size != VAS_EBOOT_DISK_SECTOR_SIZE)
{
VasEBoot_uint8_t sts = parms.taskfile.status;
VasEBoot_free (info16);
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
if ((sts & (VAS_EBOOT_ATA_STATUS_BUSY | VAS_EBOOT_ATA_STATUS_DRQ
| VAS_EBOOT_ATA_STATUS_ERR)) == VAS_EBOOT_ATA_STATUS_ERR
&& (parms.taskfile.error & 0x04 /* ABRT */))
/* Device without ATA IDENTIFY, try ATAPI. */
return VasEBoot_atapi_identify (dev);
else if (sts == 0x00)
{
*dev->present = 0;
/* No device, return error but don't print message. */
return VAS_EBOOT_ERR_UNKNOWN_DEVICE;
}
else
{
*dev->present = 0;
/* Other Error. */
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE,
"device cannot be identified");
}
}
/* Now it is certain that this is not an ATAPI device. */
dev->atapi = 0;
/* CHS is always supported. */
dev->addr = VAS_EBOOT_ATA_CHS;
/* Check if LBA is supported. */
if (info16[49] & VasEBoot_cpu_to_le16_compile_time ((1 << 9)))
{
/* Check if LBA48 is supported. */
if (info16[83] & VasEBoot_cpu_to_le16_compile_time ((1 << 10)))
dev->addr = VAS_EBOOT_ATA_LBA48;
else
dev->addr = VAS_EBOOT_ATA_LBA;
}
/* Determine the amount of sectors. */
if (dev->addr != VAS_EBOOT_ATA_LBA48)
dev->size = VasEBoot_le_to_cpu32 (info32[30]);
else
dev->size = VasEBoot_le_to_cpu64 (info64[25]);
if (info16[106] & VasEBoot_cpu_to_le16_compile_time ((1 << 12)))
{
VasEBoot_uint32_t secsize;
secsize = VasEBoot_le_to_cpu32 (VasEBoot_get_unaligned32 (&info16[117]));
if (secsize & (secsize - 1) || !secsize
|| secsize > 1048576)
secsize = 256;
dev->log_sector_size = VasEBoot_log2ull (secsize) + 1;
}
else
dev->log_sector_size = 9;
/* Read CHS information. */
dev->cylinders = VasEBoot_le_to_cpu16 (info16[1]);
dev->heads = VasEBoot_le_to_cpu16 (info16[3]);
dev->sectors_per_track = VasEBoot_le_to_cpu16 (info16[6]);
VasEBoot_ata_dumpinfo (dev, info16);
VasEBoot_free (info16);
return 0;
}
static VasEBoot_err_t
VasEBoot_ata_setaddress (struct VasEBoot_ata *dev,
struct VasEBoot_disk_ata_pass_through_parms *parms,
VasEBoot_disk_addr_t sector,
VasEBoot_size_t size,
VasEBoot_ata_addressing_t addressing)
{
switch (addressing)
{
case VAS_EBOOT_ATA_CHS:
{
unsigned int cylinder;
unsigned int head;
unsigned int sect;
if (dev->sectors_per_track == 0
|| dev->heads == 0)
return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE,
"sector %" PRIxVAS_EBOOT_UINT64_T " cannot be "
"addressed using CHS addressing",
sector);
/* Calculate the sector, cylinder and head to use. */
sect = ((VasEBoot_uint32_t) sector % dev->sectors_per_track) + 1;
cylinder = (((VasEBoot_uint32_t) sector / dev->sectors_per_track)
/ dev->heads);
head = ((VasEBoot_uint32_t) sector / dev->sectors_per_track) % dev->heads;
if (sect > dev->sectors_per_track
|| cylinder > dev->cylinders
|| head > dev->heads)
return VasEBoot_error (VAS_EBOOT_ERR_OUT_OF_RANGE,
"sector %" PRIxVAS_EBOOT_UINT64_T " cannot be "
"addressed using CHS addressing",
sector);
parms->taskfile.disk = 0xE0 | head;
parms->taskfile.sectnum = sect;
parms->taskfile.cyllsb = cylinder & 0xFF;
parms->taskfile.cylmsb = cylinder >> 8;
break;
}
case VAS_EBOOT_ATA_LBA:
if (size == 256)
size = 0;
parms->taskfile.disk = 0xE0 | ((sector >> 24) & 0x0F);
parms->taskfile.sectors = size;
parms->taskfile.lba_low = sector & 0xFF;
parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
parms->taskfile.lba_high = (sector >> 16) & 0xFF;
break;
case VAS_EBOOT_ATA_LBA48:
if (size == 65536)
size = 0;
parms->taskfile.disk = 0xE0;
/* Set "Previous". */
parms->taskfile.sectors = size & 0xFF;
parms->taskfile.lba_low = sector & 0xFF;
parms->taskfile.lba_mid = (sector >> 8) & 0xFF;
parms->taskfile.lba_high = (sector >> 16) & 0xFF;
/* Set "Current". */
parms->taskfile.sectors48 = (size >> 8) & 0xFF;
parms->taskfile.lba48_low = (sector >> 24) & 0xFF;
parms->taskfile.lba48_mid = (sector >> 32) & 0xFF;
parms->taskfile.lba48_high = (sector >> 40) & 0xFF;
break;
}
return VAS_EBOOT_ERR_NONE;
}
static VasEBoot_err_t
VasEBoot_ata_readwrite (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
VasEBoot_size_t size, char *buf, int rw)
{
struct VasEBoot_ata *ata = disk->data;
VasEBoot_ata_addressing_t addressing = ata->addr;
VasEBoot_size_t batch;
int cmd, cmd_write;
VasEBoot_size_t nsectors = 0;
VasEBoot_dprintf("ata", "VasEBoot_ata_readwrite (size=%llu, rw=%d)\n",
(unsigned long long) size, rw);
if (addressing == VAS_EBOOT_ATA_LBA48 && ((sector + size) >> 28) != 0)
{
if (ata->dma)
{
cmd = VAS_EBOOT_ATA_CMD_READ_SECTORS_DMA_EXT;
cmd_write = VAS_EBOOT_ATA_CMD_WRITE_SECTORS_DMA_EXT;
}
else
{
cmd = VAS_EBOOT_ATA_CMD_READ_SECTORS_EXT;
cmd_write = VAS_EBOOT_ATA_CMD_WRITE_SECTORS_EXT;
}
}
else
{
if (addressing == VAS_EBOOT_ATA_LBA48)
addressing = VAS_EBOOT_ATA_LBA;
if (ata->dma)
{
cmd = VAS_EBOOT_ATA_CMD_READ_SECTORS_DMA;
cmd_write = VAS_EBOOT_ATA_CMD_WRITE_SECTORS_DMA;
}
else
{
cmd = VAS_EBOOT_ATA_CMD_READ_SECTORS;
cmd_write = VAS_EBOOT_ATA_CMD_WRITE_SECTORS;
}
}
if (addressing != VAS_EBOOT_ATA_CHS)
batch = 256;
else
batch = 1;
while (nsectors < size)
{
struct VasEBoot_disk_ata_pass_through_parms parms;
VasEBoot_err_t err;
if (size - nsectors < batch)
batch = size - nsectors;
VasEBoot_dprintf("ata", "rw=%d, sector=%llu, batch=%llu\n", rw, (unsigned long long) sector, (unsigned long long) batch);
VasEBoot_memset (&parms, 0, sizeof (parms));
VasEBoot_ata_setaddress (ata, &parms, sector, batch, addressing);
parms.taskfile.cmd = (! rw ? cmd : cmd_write);
parms.buffer = buf;
parms.size = batch << ata->log_sector_size;
parms.write = rw;
if (ata->dma)
parms.dma = 1;
err = ata->dev->readwrite (ata, &parms, 0);
if (err)
return err;
if (parms.size != batch << ata->log_sector_size)
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, "incomplete read");
buf += batch << ata->log_sector_size;
sector += batch;
nsectors += batch;
}
return VAS_EBOOT_ERR_NONE;
}
static inline void
VasEBoot_ata_real_close (struct VasEBoot_ata *ata)
{
if (ata->dev->close)
ata->dev->close (ata);
}
static struct VasEBoot_ata *
VasEBoot_ata_real_open (int id, int bus)
{
struct VasEBoot_ata *ata;
VasEBoot_ata_dev_t p;
ata = VasEBoot_zalloc (sizeof (*ata));
if (!ata)
return NULL;
for (p = VasEBoot_ata_dev_list; p; p = p->next)
{
VasEBoot_err_t err;
if (p->open (id, bus, ata))
{
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
continue;
}
ata->dev = p;
/* Use the IDENTIFY DEVICE command to query the device. */
err = VasEBoot_ata_identify (ata);
if (err)
{
if (!VasEBoot_errno)
VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "no such ATA device");
VasEBoot_free (ata);
return NULL;
}
return ata;
}
VasEBoot_free (ata);
VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "no such ATA device");
return NULL;
}
/* Context for VasEBoot_ata_iterate. */
struct VasEBoot_ata_iterate_ctx
{
VasEBoot_disk_dev_iterate_hook_t hook;
void *hook_data;
};
/* Helper for VasEBoot_ata_iterate. */
static int
VasEBoot_ata_iterate_iter (int id, int bus, void *data)
{
struct VasEBoot_ata_iterate_ctx *ctx = data;
struct VasEBoot_ata *ata;
int ret;
char devname[40];
ata = VasEBoot_ata_real_open (id, bus);
if (!ata)
{
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
return 0;
}
if (ata->atapi)
{
VasEBoot_ata_real_close (ata);
return 0;
}
VasEBoot_snprintf (devname, sizeof (devname),
"%s%d", VasEBoot_scsi_names[id], bus);
ret = ctx->hook (devname, ctx->hook_data);
VasEBoot_ata_real_close (ata);
return ret;
}
static int
VasEBoot_ata_iterate (VasEBoot_disk_dev_iterate_hook_t hook, void *hook_data,
VasEBoot_disk_pull_t pull)
{
struct VasEBoot_ata_iterate_ctx ctx = { hook, hook_data };
VasEBoot_ata_dev_t p;
for (p = VasEBoot_ata_dev_list; p; p = p->next)
if (p->iterate && p->iterate (VasEBoot_ata_iterate_iter, &ctx, pull))
return 1;
return 0;
}
static VasEBoot_err_t
VasEBoot_ata_open (const char *name, VasEBoot_disk_t disk)
{
unsigned id, bus;
struct VasEBoot_ata *ata;
for (id = 0; id < VAS_EBOOT_SCSI_NUM_SUBSYSTEMS; id++)
if (VasEBoot_strncmp (VasEBoot_scsi_names[id], name,
VasEBoot_strlen (VasEBoot_scsi_names[id])) == 0
&& VasEBoot_isdigit (name[VasEBoot_strlen (VasEBoot_scsi_names[id])]))
break;
if (id == VAS_EBOOT_SCSI_NUM_SUBSYSTEMS)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
bus = VasEBoot_strtoul (name + VasEBoot_strlen (VasEBoot_scsi_names[id]), 0, 0);
ata = VasEBoot_ata_real_open (id, bus);
if (!ata)
return VasEBoot_errno;
if (ata->atapi)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "not an ATA harddisk");
disk->total_sectors = ata->size;
disk->max_agglomerate = (ata->maxbuffer >> (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS));
if (disk->max_agglomerate > (256U >> (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS - ata->log_sector_size)))
disk->max_agglomerate = (256U >> (VAS_EBOOT_DISK_CACHE_BITS + VAS_EBOOT_DISK_SECTOR_BITS - ata->log_sector_size));
disk->log_sector_size = ata->log_sector_size;
disk->id = VasEBoot_make_scsi_id (id, bus, 0);
disk->data = ata;
return 0;
}
static void
VasEBoot_ata_close (VasEBoot_disk_t disk)
{
struct VasEBoot_ata *ata = disk->data;
VasEBoot_ata_real_close (ata);
}
static VasEBoot_err_t
VasEBoot_ata_read (VasEBoot_disk_t disk, VasEBoot_disk_addr_t sector,
VasEBoot_size_t size, char *buf)
{
return VasEBoot_ata_readwrite (disk, sector, size, buf, 0);
}
static VasEBoot_err_t
VasEBoot_ata_write (VasEBoot_disk_t disk,
VasEBoot_disk_addr_t sector,
VasEBoot_size_t size,
const char *buf)
{
return VasEBoot_ata_readwrite (disk, sector, size, (char *) buf, 1);
}
static struct VasEBoot_disk_dev VasEBoot_atadisk_dev =
{
.name = "ATA",
.id = VAS_EBOOT_DISK_DEVICE_ATA_ID,
.disk_iterate = VasEBoot_ata_iterate,
.disk_open = VasEBoot_ata_open,
.disk_close = VasEBoot_ata_close,
.disk_read = VasEBoot_ata_read,
.disk_write = VasEBoot_ata_write,
.next = 0
};
/* ATAPI code. */
static VasEBoot_err_t
VasEBoot_atapi_read (struct VasEBoot_scsi *scsi, VasEBoot_size_t cmdsize, char *cmd,
VasEBoot_size_t size, char *buf)
{
struct VasEBoot_ata *dev = scsi->data;
struct VasEBoot_disk_ata_pass_through_parms parms;
VasEBoot_err_t err;
VasEBoot_dprintf("ata", "VasEBoot_atapi_read (size=%llu)\n", (unsigned long long) size);
VasEBoot_memset (&parms, 0, sizeof (parms));
parms.taskfile.disk = 0;
parms.taskfile.features = 0;
parms.taskfile.atapi_ireason = 0;
parms.taskfile.atapi_cnthigh = size >> 8;
parms.taskfile.atapi_cntlow = size & 0xff;
parms.taskfile.cmd = VAS_EBOOT_ATA_CMD_PACKET;
parms.cmd = cmd;
parms.cmdsize = cmdsize;
parms.size = size;
parms.buffer = buf;
err = dev->dev->readwrite (dev, &parms, 0);
if (err)
return err;
if (parms.size != size)
return VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, "incomplete ATAPI read");
return VAS_EBOOT_ERR_NONE;
}
static VasEBoot_err_t
VasEBoot_atapi_write (struct VasEBoot_scsi *scsi __attribute__((unused)),
VasEBoot_size_t cmdsize __attribute__((unused)),
char *cmd __attribute__((unused)),
VasEBoot_size_t size __attribute__((unused)),
const char *buf __attribute__((unused)))
{
// XXX: scsi.mod does not use write yet.
return VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "ATAPI write not implemented");
}
static VasEBoot_err_t
VasEBoot_atapi_open (int id, int bus, struct VasEBoot_scsi *scsi)
{
struct VasEBoot_ata *ata;
ata = VasEBoot_ata_real_open (id, bus);
if (!ata)
return VasEBoot_errno;
if (! ata->atapi)
return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "no such ATAPI device");
scsi->data = ata;
scsi->luns = 1;
return VAS_EBOOT_ERR_NONE;
}
/* Context for VasEBoot_atapi_iterate. */
struct VasEBoot_atapi_iterate_ctx
{
VasEBoot_scsi_dev_iterate_hook_t hook;
void *hook_data;
};
/* Helper for VasEBoot_atapi_iterate. */
static int
VasEBoot_atapi_iterate_iter (int id, int bus, void *data)
{
struct VasEBoot_atapi_iterate_ctx *ctx = data;
struct VasEBoot_ata *ata;
int ret;
ata = VasEBoot_ata_real_open (id, bus);
if (!ata)
{
VasEBoot_errno = VAS_EBOOT_ERR_NONE;
return 0;
}
if (!ata->atapi)
{
VasEBoot_ata_real_close (ata);
return 0;
}
ret = ctx->hook (id, bus, 1, ctx->hook_data);
VasEBoot_ata_real_close (ata);
return ret;
}
static int
VasEBoot_atapi_iterate (VasEBoot_scsi_dev_iterate_hook_t hook, void *hook_data,
VasEBoot_disk_pull_t pull)
{
struct VasEBoot_atapi_iterate_ctx ctx = { hook, hook_data };
VasEBoot_ata_dev_t p;
for (p = VasEBoot_ata_dev_list; p; p = p->next)
if (p->iterate && p->iterate (VasEBoot_atapi_iterate_iter, &ctx, pull))
return 1;
return 0;
}
static void
VasEBoot_atapi_close (VasEBoot_scsi_t disk)
{
struct VasEBoot_ata *ata = disk->data;
VasEBoot_ata_real_close (ata);
}
void
VasEBoot_ata_dev_register (VasEBoot_ata_dev_t dev)
{
dev->next = VasEBoot_ata_dev_list;
VasEBoot_ata_dev_list = dev;
}
void
VasEBoot_ata_dev_unregister (VasEBoot_ata_dev_t dev)
{
VasEBoot_ata_dev_t *p, q;
for (p = &VasEBoot_ata_dev_list, q = *p; q; p = &(q->next), q = q->next)
if (q == dev)
{
*p = q->next;
break;
}
}
static struct VasEBoot_scsi_dev VasEBoot_atapi_dev =
{
.iterate = VasEBoot_atapi_iterate,
.open = VasEBoot_atapi_open,
.close = VasEBoot_atapi_close,
.read = VasEBoot_atapi_read,
.write = VasEBoot_atapi_write,
.next = 0
};
VAS_EBOOT_MOD_INIT(ata)
{
VasEBoot_disk_dev_register (&VasEBoot_atadisk_dev);
/* ATAPI devices are handled by scsi.mod. */
VasEBoot_scsi_dev_register (&VasEBoot_atapi_dev);
}
VAS_EBOOT_MOD_FINI(ata)
{
VasEBoot_scsi_dev_unregister (&VasEBoot_atapi_dev);
VasEBoot_disk_dev_unregister (&VasEBoot_atadisk_dev);
}