/* 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 . */ #include #include #include #include #include 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); }