/* ata.c - ATA disk access. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2007, 2008, 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 . */ #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); static grub_ata_dev_t grub_ata_dev_list; /* Byteorder has to be changed before strings can be read. */ static void grub_ata_strncpy (grub_uint16_t *dst16, grub_uint16_t *src16, grub_size_t len) { unsigned int i; for (i = 0; i < len / 2; i++) *(dst16++) = grub_swap_bytes16 (*(src16++)); *dst16 = 0; } static void grub_ata_dumpinfo (struct grub_ata *dev, grub_uint16_t *info) { grub_uint16_t text[21]; /* The device information was read, dump it for debugging. */ grub_ata_strncpy (text, info + 10, 20); grub_dprintf ("ata", "Serial: %s\n", (char *) text); grub_ata_strncpy (text, info + 23, 8); grub_dprintf ("ata", "Firmware: %s\n", (char *) text); grub_ata_strncpy (text, info + 27, 40); grub_dprintf ("ata", "Model: %s\n", (char *) text); if (! dev->atapi) { grub_dprintf ("ata", "Addressing: %d\n", dev->addr); grub_dprintf ("ata", "Sectors: %lld\n", (unsigned long long) dev->size); grub_dprintf ("ata", "Sector size: %u\n", 1U << dev->log_sector_size); } } static grub_err_t grub_atapi_identify (struct grub_ata *dev) { struct grub_disk_ata_pass_through_parms parms; grub_uint16_t *info; grub_err_t err; info = grub_malloc (GRUB_DISK_SECTOR_SIZE); if (! info) return grub_errno; grub_memset (&parms, 0, sizeof (parms)); parms.taskfile.disk = 0xE0; parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_PACKET_DEVICE; parms.size = GRUB_DISK_SECTOR_SIZE; parms.buffer = info; err = dev->dev->readwrite (dev, &parms, *dev->present); if (err) { *dev->present = 0; return err; } if (parms.size != GRUB_DISK_SECTOR_SIZE) { *dev->present = 0; return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "device cannot be identified"); } dev->atapi = 1; grub_ata_dumpinfo (dev, info); grub_free (info); return GRUB_ERR_NONE; } static grub_err_t grub_ata_identify (struct grub_ata *dev) { struct grub_disk_ata_pass_through_parms parms; grub_uint64_t *info64; grub_uint32_t *info32; grub_uint16_t *info16; grub_err_t err; if (dev->atapi) return grub_atapi_identify (dev); info64 = grub_malloc (GRUB_DISK_SECTOR_SIZE); info32 = (grub_uint32_t *) info64; info16 = (grub_uint16_t *) info64; if (! info16) return grub_errno; grub_memset (&parms, 0, sizeof (parms)); parms.buffer = info16; parms.size = GRUB_DISK_SECTOR_SIZE; parms.taskfile.disk = 0xE0; parms.taskfile.cmd = GRUB_ATA_CMD_IDENTIFY_DEVICE; err = dev->dev->readwrite (dev, &parms, *dev->present); if (err || parms.size != GRUB_DISK_SECTOR_SIZE) { grub_uint8_t sts = parms.taskfile.status; grub_free (info16); grub_errno = GRUB_ERR_NONE; if ((sts & (GRUB_ATA_STATUS_BUSY | GRUB_ATA_STATUS_DRQ | GRUB_ATA_STATUS_ERR)) == GRUB_ATA_STATUS_ERR && (parms.taskfile.error & 0x04 /* ABRT */)) /* Device without ATA IDENTIFY, try ATAPI. */ return grub_atapi_identify (dev); else if (sts == 0x00) { *dev->present = 0; /* No device, return error but don't print message. */ return GRUB_ERR_UNKNOWN_DEVICE; } else { *dev->present = 0; /* Other Error. */ return grub_error (GRUB_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 = GRUB_ATA_CHS; /* Check if LBA is supported. */ if (info16[49] & grub_cpu_to_le16_compile_time ((1 << 9))) { /* Check if LBA48 is supported. */ if (info16[83] & grub_cpu_to_le16_compile_time ((1 << 10))) dev->addr = GRUB_ATA_LBA48; else dev->addr = GRUB_ATA_LBA; } /* Determine the amount of sectors. */ if (dev->addr != GRUB_ATA_LBA48) dev->size = grub_le_to_cpu32 (info32[30]); else dev->size = grub_le_to_cpu64 (info64[25]); if (info16[106] & grub_cpu_to_le16_compile_time ((1 << 12))) { grub_uint32_t secsize; secsize = grub_le_to_cpu32 (grub_get_unaligned32 (&info16[117])); if (secsize & (secsize - 1) || !secsize || secsize > 1048576) secsize = 256; for (dev->log_sector_size = 0; (1U << dev->log_sector_size) < secsize; dev->log_sector_size++); dev->log_sector_size++; } else dev->log_sector_size = 9; /* Read CHS information. */ dev->cylinders = grub_le_to_cpu16 (info16[1]); dev->heads = grub_le_to_cpu16 (info16[3]); dev->sectors_per_track = grub_le_to_cpu16 (info16[6]); grub_ata_dumpinfo (dev, info16); grub_free (info16); return 0; } static grub_err_t grub_ata_setaddress (struct grub_ata *dev, struct grub_disk_ata_pass_through_parms *parms, grub_disk_addr_t sector, grub_size_t size, grub_ata_addressing_t addressing) { switch (addressing) { case GRUB_ATA_CHS: { unsigned int cylinder; unsigned int head; unsigned int sect; if (dev->sectors_per_track == 0 || dev->heads == 0) return grub_error (GRUB_ERR_OUT_OF_RANGE, "sector %d cannot be addressed " "using CHS addressing", sector); /* Calculate the sector, cylinder and head to use. */ sect = ((grub_uint32_t) sector % dev->sectors_per_track) + 1; cylinder = (((grub_uint32_t) sector / dev->sectors_per_track) / dev->heads); head = ((grub_uint32_t) sector / dev->sectors_per_track) % dev->heads; if (sect > dev->sectors_per_track || cylinder > dev->cylinders || head > dev->heads) return grub_error (GRUB_ERR_OUT_OF_RANGE, "sector %d 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 GRUB_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 GRUB_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 GRUB_ERR_NONE; } static grub_err_t grub_ata_readwrite (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t size, char *buf, int rw) { struct grub_ata *ata = disk->data; grub_ata_addressing_t addressing = ata->addr; grub_size_t batch; int cmd, cmd_write; grub_size_t nsectors = 0; grub_dprintf("ata", "grub_ata_readwrite (size=%llu, rw=%d)\n", (unsigned long long) size, rw); if (addressing == GRUB_ATA_LBA48 && ((sector + size) >> 28) != 0) { if (ata->dma) { cmd = GRUB_ATA_CMD_READ_SECTORS_DMA_EXT; cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_DMA_EXT; } else { cmd = GRUB_ATA_CMD_READ_SECTORS_EXT; cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_EXT; } } else { if (addressing == GRUB_ATA_LBA48) addressing = GRUB_ATA_LBA; if (ata->dma) { cmd = GRUB_ATA_CMD_READ_SECTORS_DMA; cmd_write = GRUB_ATA_CMD_WRITE_SECTORS_DMA; } else { cmd = GRUB_ATA_CMD_READ_SECTORS; cmd_write = GRUB_ATA_CMD_WRITE_SECTORS; } } if (addressing != GRUB_ATA_CHS) batch = 256; else batch = 1; while (nsectors < size) { struct grub_disk_ata_pass_through_parms parms; grub_err_t err; if (size - nsectors < batch) batch = size - nsectors; grub_dprintf("ata", "rw=%d, sector=%llu, batch=%llu\n", rw, (unsigned long long) sector, (unsigned long long) batch); grub_memset (&parms, 0, sizeof (parms)); grub_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 grub_error (GRUB_ERR_READ_ERROR, "incomplete read"); buf += batch << ata->log_sector_size; sector += batch; nsectors += batch; } return GRUB_ERR_NONE; } static inline void grub_ata_real_close (struct grub_ata *ata) { if (ata->dev->close) ata->dev->close (ata); } static struct grub_ata * grub_ata_real_open (int id, int bus) { struct grub_ata *ata; grub_ata_dev_t p; ata = grub_zalloc (sizeof (*ata)); if (!ata) return NULL; for (p = grub_ata_dev_list; p; p = p->next) { grub_err_t err; if (p->open (id, bus, ata)) { grub_errno = GRUB_ERR_NONE; continue; } ata->dev = p; /* Use the IDENTIFY DEVICE command to query the device. */ err = grub_ata_identify (ata); if (err) { if (!grub_errno) grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device"); grub_free (ata); return NULL; } return ata; } grub_free (ata); grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATA device"); return NULL; } /* Context for grub_ata_iterate. */ struct grub_ata_iterate_ctx { grub_disk_dev_iterate_hook_t hook; void *hook_data; }; /* Helper for grub_ata_iterate. */ static int grub_ata_iterate_iter (int id, int bus, void *data) { struct grub_ata_iterate_ctx *ctx = data; struct grub_ata *ata; int ret; char devname[40]; ata = grub_ata_real_open (id, bus); if (!ata) { grub_errno = GRUB_ERR_NONE; return 0; } if (ata->atapi) { grub_ata_real_close (ata); return 0; } grub_snprintf (devname, sizeof (devname), "%s%d", grub_scsi_names[id], bus); ret = ctx->hook (devname, ctx->hook_data); grub_ata_real_close (ata); return ret; } static int grub_ata_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, grub_disk_pull_t pull) { struct grub_ata_iterate_ctx ctx = { hook, hook_data }; grub_ata_dev_t p; for (p = grub_ata_dev_list; p; p = p->next) if (p->iterate && p->iterate (grub_ata_iterate_iter, &ctx, pull)) return 1; return 0; } static grub_err_t grub_ata_open (const char *name, grub_disk_t disk) { unsigned id, bus; struct grub_ata *ata; for (id = 0; id < GRUB_SCSI_NUM_SUBSYSTEMS; id++) if (grub_strncmp (grub_scsi_names[id], name, grub_strlen (grub_scsi_names[id])) == 0 && grub_isdigit (name[grub_strlen (grub_scsi_names[id])])) break; if (id == GRUB_SCSI_NUM_SUBSYSTEMS) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk"); bus = grub_strtoul (name + grub_strlen (grub_scsi_names[id]), 0, 0); ata = grub_ata_real_open (id, bus); if (!ata) return grub_errno; if (ata->atapi) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an ATA harddisk"); disk->total_sectors = ata->size; disk->max_agglomerate = (ata->maxbuffer >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS)); if (disk->max_agglomerate > (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size))) disk->max_agglomerate = (256U >> (GRUB_DISK_CACHE_BITS + GRUB_DISK_SECTOR_BITS - ata->log_sector_size)); disk->log_sector_size = ata->log_sector_size; disk->id = grub_make_scsi_id (id, bus, 0); disk->data = ata; return 0; } static void grub_ata_close (grub_disk_t disk) { struct grub_ata *ata = disk->data; grub_ata_real_close (ata); } static grub_err_t grub_ata_read (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t size, char *buf) { return grub_ata_readwrite (disk, sector, size, buf, 0); } static grub_err_t grub_ata_write (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t size, const char *buf) { return grub_ata_readwrite (disk, sector, size, (char *) buf, 1); } static struct grub_disk_dev grub_atadisk_dev = { .name = "ATA", .id = GRUB_DISK_DEVICE_ATA_ID, .disk_iterate = grub_ata_iterate, .disk_open = grub_ata_open, .disk_close = grub_ata_close, .disk_read = grub_ata_read, .disk_write = grub_ata_write, .next = 0 }; /* ATAPI code. */ static grub_err_t grub_atapi_read (struct grub_scsi *scsi, grub_size_t cmdsize, char *cmd, grub_size_t size, char *buf) { struct grub_ata *dev = scsi->data; struct grub_disk_ata_pass_through_parms parms; grub_err_t err; grub_dprintf("ata", "grub_atapi_read (size=%llu)\n", (unsigned long long) size); grub_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 = GRUB_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 grub_error (GRUB_ERR_READ_ERROR, "incomplete ATAPI read"); return GRUB_ERR_NONE; } static grub_err_t grub_atapi_write (struct grub_scsi *scsi __attribute__((unused)), grub_size_t cmdsize __attribute__((unused)), char *cmd __attribute__((unused)), grub_size_t size __attribute__((unused)), const char *buf __attribute__((unused))) { // XXX: scsi.mod does not use write yet. return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "ATAPI write not implemented"); } static grub_err_t grub_atapi_open (int id, int bus, struct grub_scsi *scsi) { struct grub_ata *ata; ata = grub_ata_real_open (id, bus); if (!ata) return grub_errno; if (! ata->atapi) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no such ATAPI device"); scsi->data = ata; scsi->luns = 1; return GRUB_ERR_NONE; } /* Context for grub_atapi_iterate. */ struct grub_atapi_iterate_ctx { grub_scsi_dev_iterate_hook_t hook; void *hook_data; }; /* Helper for grub_atapi_iterate. */ static int grub_atapi_iterate_iter (int id, int bus, void *data) { struct grub_atapi_iterate_ctx *ctx = data; struct grub_ata *ata; int ret; ata = grub_ata_real_open (id, bus); if (!ata) { grub_errno = GRUB_ERR_NONE; return 0; } if (!ata->atapi) { grub_ata_real_close (ata); return 0; } ret = ctx->hook (id, bus, 1, ctx->hook_data); grub_ata_real_close (ata); return ret; } static int grub_atapi_iterate (grub_scsi_dev_iterate_hook_t hook, void *hook_data, grub_disk_pull_t pull) { struct grub_atapi_iterate_ctx ctx = { hook, hook_data }; grub_ata_dev_t p; for (p = grub_ata_dev_list; p; p = p->next) if (p->iterate && p->iterate (grub_atapi_iterate_iter, &ctx, pull)) return 1; return 0; } static void grub_atapi_close (grub_scsi_t disk) { struct grub_ata *ata = disk->data; grub_ata_real_close (ata); } void grub_ata_dev_register (grub_ata_dev_t dev) { dev->next = grub_ata_dev_list; grub_ata_dev_list = dev; } void grub_ata_dev_unregister (grub_ata_dev_t dev) { grub_ata_dev_t *p, q; for (p = &grub_ata_dev_list, q = *p; q; p = &(q->next), q = q->next) if (q == dev) { *p = q->next; break; } } static struct grub_scsi_dev grub_atapi_dev = { .iterate = grub_atapi_iterate, .open = grub_atapi_open, .close = grub_atapi_close, .read = grub_atapi_read, .write = grub_atapi_write, .next = 0 }; GRUB_MOD_INIT(ata) { grub_disk_dev_register (&grub_atadisk_dev); /* ATAPI devices are handled by scsi.mod. */ grub_scsi_dev_register (&grub_atapi_dev); } GRUB_MOD_FINI(ata) { grub_scsi_dev_unregister (&grub_atapi_dev); grub_disk_dev_unregister (&grub_atadisk_dev); }