diff options
Diffstat (limited to 'com32/lib/syslinux/disk.c')
-rw-r--r-- | com32/lib/syslinux/disk.c | 534 |
1 files changed, 534 insertions, 0 deletions
diff --git a/com32/lib/syslinux/disk.c b/com32/lib/syslinux/disk.c new file mode 100644 index 00000000..d6409af6 --- /dev/null +++ b/com32/lib/syslinux/disk.c @@ -0,0 +1,534 @@ +/* ----------------------------------------------------------------------- * + * + * Copyright 2003-2009 H. Peter Anvin - All Rights Reserved + * Copyright 2009-2010 Intel Corporation; author: H. Peter Anvin + * Copyright (C) 2010 Shao Miller + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom + * the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall + * be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ----------------------------------------------------------------------- */ + +/** + * @file disk.c + * + * Deal with disks and partitions + */ + +#include <dprintf.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslinux/disk.h> + +/** + * Call int 13h, but with retry on failure. Especially floppies need this. + * + * @v inreg CPU register settings upon INT call + * @v outreg CPU register settings returned by INT call + * @ret (int) 0 upon success, -1 upon failure + */ +int disk_int13_retry(const com32sys_t * inreg, com32sys_t * outreg) +{ + int retry = 6; /* Number of retries */ + com32sys_t tmpregs; + + if (!outreg) + outreg = &tmpregs; + + while (retry--) { + __intcall(0x13, inreg, outreg); + if (!(outreg->eflags.l & EFLAGS_CF)) + return 0; /* CF=0, OK */ + } + + return -1; /* Error */ +} + +/** + * Query disk parameters and EBIOS availability for a particular disk. + * + * @v disk The INT 0x13 disk drive number to process + * @v diskinfo The structure to save the queried params to + * @ret (int) 0 upon success, -1 upon failure + */ +int disk_get_params(int disk, struct disk_info *const diskinfo) +{ + static com32sys_t inreg, outreg; + struct disk_ebios_eparam *eparam = __com32.cs_bounce; + + memset(diskinfo, 0, sizeof *diskinfo); + diskinfo->disk = disk; + diskinfo->bps = SECTOR; + + /* Get EBIOS support */ + memset(&inreg, 0, sizeof inreg); + inreg.eax.b[1] = 0x41; + inreg.ebx.w[0] = 0x55aa; + inreg.edx.b[0] = disk; + inreg.eflags.b[0] = 0x3; /* CF set */ + + __intcall(0x13, &inreg, &outreg); + + if (!(outreg.eflags.l & EFLAGS_CF) && + outreg.ebx.w[0] == 0xaa55 && (outreg.ecx.b[0] & 1)) { + diskinfo->ebios = 1; + } + + /* Get extended disk parameters if ebios == 1 */ + if (diskinfo->ebios) { + memset(&inreg, 0, sizeof inreg); + inreg.eax.b[1] = 0x48; + inreg.edx.b[0] = disk; + inreg.esi.w[0] = OFFS(eparam); + inreg.ds = SEG(eparam); + + memset(eparam, 0, sizeof *eparam); + eparam->len = sizeof *eparam; + + __intcall(0x13, &inreg, &outreg); + + if (!(outreg.eflags.l & EFLAGS_CF)) { + diskinfo->lbacnt = eparam->lbacnt; + if (eparam->bps) + diskinfo->bps = eparam->bps; + /* + * don't think about using geometry data returned by + * 48h, as it can differ from 08h a lot ... + */ + } + } + /* + * Get disk parameters the old way - really only useful for hard + * disks, but if we have a partitioned floppy it's actually our best + * chance... + */ + memset(&inreg, 0, sizeof inreg); + inreg.eax.b[1] = 0x08; + inreg.edx.b[0] = disk; + + __intcall(0x13, &inreg, &outreg); + + if (outreg.eflags.l & EFLAGS_CF) + return diskinfo->ebios ? 0 : -1; + + diskinfo->spt = 0x3f & outreg.ecx.b[0]; + diskinfo->head = 1 + outreg.edx.b[1]; + diskinfo->cyl = 1 + (outreg.ecx.b[1] | ((outreg.ecx.b[0] & 0xc0u) << 2)); + + if (diskinfo->spt) + diskinfo->cbios = 1; /* Valid geometry */ + else { + diskinfo->head = 1; + diskinfo->spt = 1; + diskinfo->cyl = 1; + } + + if (!diskinfo->lbacnt) + diskinfo->lbacnt = diskinfo->cyl * diskinfo->head * diskinfo->spt; + + return 0; +} + +/** + * Get disk block(s) and return a malloc'd buffer. + * + * @v diskinfo The disk drive to read from + * @v lba The logical block address to begin reading at + * @v count The number of sectors to read + * @ret data An allocated buffer with the read data + * + * Uses the disk number and information from diskinfo. Read count sectors + * from drive, starting at lba. Return a new buffer, or NULL upon failure. + */ +void *disk_read_sectors(const struct disk_info *const diskinfo, uint64_t lba, + uint8_t count) +{ + com32sys_t inreg; + struct disk_ebios_dapa *dapa = __com32.cs_bounce; + void *buf = (char *)__com32.cs_bounce + diskinfo->bps; + void *data; + uint32_t maxcnt; + + maxcnt = (__com32.cs_bounce_size - diskinfo->bps) / diskinfo->bps; + if (!count || count > maxcnt || lba + count > diskinfo->lbacnt) + return NULL; + + memset(&inreg, 0, sizeof inreg); + + if (diskinfo->ebios) { + dapa->len = sizeof(*dapa); + dapa->count = count; + dapa->off = OFFS(buf); + dapa->seg = SEG(buf); + dapa->lba = lba; + + inreg.esi.w[0] = OFFS(dapa); + inreg.ds = SEG(dapa); + inreg.edx.b[0] = diskinfo->disk; + inreg.eax.b[1] = 0x42; /* Extended read */ + } else { + unsigned int c, h, s, t; + /* + * if we passed lba + count check and we get here, that means that + * lbacnt was calculated from chs geometry (or faked from 1/1/1), thus + * 32bits are perfectly enough and lbacnt corresponds to cylinder + * boundary + */ + s = lba % diskinfo->spt; + t = lba / diskinfo->spt; + h = t % diskinfo->head; + c = t / diskinfo->head; + + inreg.eax.b[0] = count; + inreg.eax.b[1] = 0x02; /* Read */ + inreg.ecx.b[1] = c; + inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1); + inreg.edx.b[1] = h; + inreg.edx.b[0] = diskinfo->disk; + inreg.ebx.w[0] = OFFS(buf); + inreg.es = SEG(buf); + } + + if (disk_int13_retry(&inreg, NULL)) + return NULL; + + data = malloc(count * diskinfo->bps); + if (data) + memcpy(data, buf, count * diskinfo->bps); + return data; +} + +/** + * Write disk block(s). + * + * @v diskinfo The disk drive to write to + * @v lba The logical block address to begin writing at + * @v data The data to write + * @v count The number of sectors to write + * @ret (int) 0 upon success, -1 upon failure + * + * Uses the disk number and information from diskinfo. + * Write sector(s) to a disk drive, starting at lba. + */ +int disk_write_sectors(const struct disk_info *const diskinfo, uint64_t lba, + const void *data, uint8_t count) +{ + com32sys_t inreg; + struct disk_ebios_dapa *dapa = __com32.cs_bounce; + void *buf = (char *)__com32.cs_bounce + diskinfo->bps; + uint32_t maxcnt; + + maxcnt = (__com32.cs_bounce_size - diskinfo->bps) / diskinfo->bps; + if (!count || count > maxcnt || lba + count > diskinfo->lbacnt) + return -1; + + memcpy(buf, data, count * diskinfo->bps); + memset(&inreg, 0, sizeof inreg); + + if (diskinfo->ebios) { + dapa->len = sizeof(*dapa); + dapa->count = count; + dapa->off = OFFS(buf); + dapa->seg = SEG(buf); + dapa->lba = lba; + + inreg.esi.w[0] = OFFS(dapa); + inreg.ds = SEG(dapa); + inreg.edx.b[0] = diskinfo->disk; + inreg.eax.b[1] = 0x43; /* Extended write */ + } else { + unsigned int c, h, s, t; + /* + * if we passed lba + count check and we get here, that means that + * lbacnt was calculated from chs geometry (or faked from 1/1/1), thus + * 32bits are perfectly enough and lbacnt corresponds to cylinder + * boundary + */ + s = lba % diskinfo->spt; + t = lba / diskinfo->spt; + h = t % diskinfo->head; + c = t / diskinfo->head; + + inreg.eax.b[0] = count; + inreg.eax.b[1] = 0x03; /* Write */ + inreg.ecx.b[1] = c; + inreg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1); + inreg.edx.b[1] = h; + inreg.edx.b[0] = diskinfo->disk; + inreg.ebx.w[0] = OFFS(buf); + inreg.es = SEG(buf); + } + + if (disk_int13_retry(&inreg, NULL)) + return -1; + + return 0; /* ok */ +} + +/** + * Write disk blocks and verify they were written. + * + * @v diskinfo The disk drive to write to + * @v lba The logical block address to begin writing at + * @v buf The data to write + * @v count The number of sectors to write + * @ret rv 0 upon success, -1 upon failure + * + * Uses the disk number and information from diskinfo. + * Writes sectors to a disk drive starting at lba, then reads them back + * to verify they were written correctly. + */ +int disk_write_verify_sectors(const struct disk_info *const diskinfo, + uint64_t lba, const void *buf, uint8_t count) +{ + char *rb; + int rv; + + rv = disk_write_sectors(diskinfo, lba, buf, count); + if (rv) + return rv; /* Write failure */ + rb = disk_read_sectors(diskinfo, lba, count); + if (!rb) + return -1; /* Readback failure */ + rv = memcmp(buf, rb, count * diskinfo->bps); + free(rb); + return rv ? -1 : 0; +} + +/** + * Dump info about a DOS partition entry + * + * @v part The 16-byte partition entry to examine + */ +void disk_dos_part_dump(const struct disk_dos_part_entry *const part) +{ + (void)part; + dprintf("Partition status _____ : 0x%.2x\n" + "Partition CHS start\n" + " Cylinder ___________ : 0x%.4x (%u)\n" + " Head _______________ : 0x%.2x (%u)\n" + " Sector _____________ : 0x%.2x (%u)\n" + "Partition type _______ : 0x%.2x\n" + "Partition CHS end\n" + " Cylinder ___________ : 0x%.4x (%u)\n" + " Head _______________ : 0x%.2x (%u)\n" + " Sector _____________ : 0x%.2x (%u)\n" + "Partition LBA start __ : 0x%.8x (%u)\n" + "Partition LBA count __ : 0x%.8x (%u)\n" + "-------------------------------\n", + part->active_flag, + chs_cylinder(part->start), + chs_cylinder(part->start), + chs_head(part->start), + chs_head(part->start), + chs_sector(part->start), + chs_sector(part->start), + part->ostype, + chs_cylinder(part->end), + chs_cylinder(part->end), + chs_head(part->end), + chs_head(part->end), + chs_sector(part->end), + chs_sector(part->end), + part->start_lba, part->start_lba, part->length, part->length); +} + +/* Trivial error message output */ +static inline void error(const char *msg) +{ + fputs(msg, stderr); +} + +/** + * This walk-map effectively reverses the little-endian + * portions of a GPT disk/partition GUID for a string representation. + * There might be a better header for this... + */ +static const char guid_le_walk_map[] = { + 3, -1, -1, -1, 0, + 5, -1, 0, + 3, -1, 0, + 2, 1, 0, + 1, 1, 1, 1, 1, 1 +}; + +/** + * Fill a buffer with a textual GUID representation. + * + * @v buf Points to a minimum array of 37 chars + * @v id The GUID to represent as text + * + * The buffer must be >= char[37] and will be populated + * with an ASCII NUL C string terminator. + * Example: 11111111-2222-3333-4444-444444444444 + * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB + */ +void guid_to_str(char *buf, const struct guid *const id) +{ + unsigned int i = 0; + const char *walker = (const char *)id; + + while (i < sizeof(guid_le_walk_map)) { + walker += guid_le_walk_map[i]; + if (!guid_le_walk_map[i]) + *buf = '-'; + else { + *buf = ((*walker & 0xF0) >> 4) + '0'; + if (*buf > '9') + *buf += 'A' - '9' - 1; + buf++; + *buf = (*walker & 0x0F) + '0'; + if (*buf > '9') + *buf += 'A' - '9' - 1; + } + buf++; + i++; + } + *buf = 0; +} + +/** + * Create a GUID structure from a textual GUID representation. + * + * @v buf Points to a GUID string to parse + * @v id Points to a GUID to be populated + * @ret (int) Returns 0 upon success, -1 upon failure + * + * The input buffer must be >= 32 hexadecimal chars and be + * terminated with an ASCII NUL. Returns non-zero on failure. + * Example: 11111111-2222-3333-4444-444444444444 + * Endian: LLLLLLLL-LLLL-LLLL-BBBB-BBBBBBBBBBBB + */ +int str_to_guid(const char *buf, struct guid *const id) +{ + char guid_seq[sizeof(struct guid) * 2]; + unsigned int i = 0; + char *walker = (char *)id; + + while (*buf && i < sizeof(guid_seq)) { + switch (*buf) { + /* Skip these three characters */ + case '{': + case '}': + case '-': + break; + default: + /* Copy something useful to the temp. sequence */ + if ((*buf >= '0') && (*buf <= '9')) + guid_seq[i] = *buf - '0'; + else if ((*buf >= 'A') && (*buf <= 'F')) + guid_seq[i] = *buf - 'A' + 10; + else if ((*buf >= 'a') && (*buf <= 'f')) + guid_seq[i] = *buf - 'a' + 10; + else { + /* Or not */ + error("Illegal character in GUID!\n"); + return -1; + } + i++; + } + buf++; + } + /* Check for insufficient valid characters */ + if (i < sizeof(guid_seq)) { + error("Too few GUID characters!\n"); + return -1; + } + buf = guid_seq; + i = 0; + while (i < sizeof(guid_le_walk_map)) { + if (!guid_le_walk_map[i]) + i++; + walker += guid_le_walk_map[i]; + *walker = *buf << 4; + buf++; + *walker |= *buf; + buf++; + i++; + } + return 0; +} + +/** + * Display GPT partition details. + * + * @v gpt_part The GPT partition entry to display + */ +void disk_gpt_part_dump(const struct disk_gpt_part_entry *const gpt_part) +{ + unsigned int i; + char guid_text[37]; + + dprintf("----------------------------------\n" + "GPT part. LBA first __ : 0x%.16llx\n" + "GPT part. LBA last ___ : 0x%.16llx\n" + "GPT part. attribs ____ : 0x%.16llx\n" + "GPT part. name _______ : '", + gpt_part->lba_first, gpt_part->lba_last, gpt_part->attribs); + for (i = 0; i < sizeof(gpt_part->name); i++) { + if (gpt_part->name[i]) + dprintf("%c", gpt_part->name[i]); + } + dprintf("'"); + guid_to_str(guid_text, &gpt_part->type); + dprintf("GPT part. type GUID __ : {%s}\n", guid_text); + guid_to_str(guid_text, &gpt_part->uid); + dprintf("GPT part. unique ID __ : {%s}\n", guid_text); +} + +/** + * Display GPT header details. + * + * @v gpt The GPT header to display + */ +void disk_gpt_header_dump(const struct disk_gpt_header *const gpt) +{ + char guid_text[37]; + + printf("GPT sig ______________ : '%8.8s'\n" + "GPT major revision ___ : 0x%.4x\n" + "GPT minor revision ___ : 0x%.4x\n" + "GPT header size ______ : 0x%.8x\n" + "GPT header checksum __ : 0x%.8x\n" + "GPT reserved _________ : '%4.4s'\n" + "GPT LBA current ______ : 0x%.16llx\n" + "GPT LBA alternative __ : 0x%.16llx\n" + "GPT LBA first usable _ : 0x%.16llx\n" + "GPT LBA last usable __ : 0x%.16llx\n" + "GPT LBA part. table __ : 0x%.16llx\n" + "GPT partition count __ : 0x%.8x\n" + "GPT partition size ___ : 0x%.8x\n" + "GPT part. table chksum : 0x%.8x\n", + gpt->sig, + gpt->rev.fields.major, + gpt->rev.fields.minor, + gpt->hdr_size, + gpt->chksum, + gpt->reserved1, + gpt->lba_cur, + gpt->lba_alt, + gpt->lba_first_usable, + gpt->lba_last_usable, + gpt->lba_table, gpt->part_count, gpt->part_size, gpt->table_chksum); + guid_to_str(guid_text, &gpt->disk_guid); + printf("GPT disk GUID ________ : {%s}\n", guid_text); +} |