blob: 5a99bb42d5cda155a612497bb890e728361b3f6a [file] [log] [blame]
/* ----------------------------------------------------------------------- *
*
* 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 <core.h>
#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;
int rv = 0;
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;
}
eparam = lmalloc(sizeof *eparam);
if (!eparam)
return -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) {
rv = diskinfo->ebios ? 0 : -1;
goto out;
}
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;
out:
lfree(eparam);
return rv;
}
/**
* Fill inreg based on EBIOS addressing properties.
*
* @v diskinfo The disk drive to read from
* @v inreg Register data structure to be filled.
* @v lba The logical block address to begin reading at
* @v count The number of sectors to read
* @v op_code Code to write/read operation
* @ret lmalloc'd buf upon success, NULL upon failure
*/
static void *ebios_setup(const struct disk_info *const diskinfo, com32sys_t *inreg,
uint64_t lba, uint8_t count, uint8_t op_code)
{
static struct disk_ebios_dapa *dapa = NULL;
void *buf;
if (!dapa) {
dapa = lmalloc(sizeof *dapa);
if (!dapa)
return NULL;
}
buf = lmalloc(count * diskinfo->bps);
if (!buf)
return NULL;
dapa->len = sizeof(*dapa);
dapa->count = count;
dapa->off = OFFS(buf);
dapa->seg = SEG(buf);
dapa->lba = lba;
inreg->eax.b[1] = op_code;
inreg->esi.w[0] = OFFS(dapa);
inreg->ds = SEG(dapa);
inreg->edx.b[0] = diskinfo->disk;
return buf;
}
/**
* Fill inreg based on CHS addressing properties.
*
* @v diskinfo The disk drive to read from
* @v inreg Register data structure to be filled.
* @v lba The logical block address to begin reading at
* @v count The number of sectors to read
* @v op_code Code to write/read operation
* @ret lmalloc'd buf upon success, NULL upon failure
*/
static void *chs_setup(const struct disk_info *const diskinfo, com32sys_t *inreg,
uint64_t lba, uint8_t count, uint8_t op_code)
{
unsigned int c, h, s, t;
void *buf;
buf = lmalloc(count * diskinfo->bps);
if (!buf)
return NULL;
/*
* 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;
memset(inreg, 0, sizeof *inreg);
inreg->eax.b[0] = count;
inreg->eax.b[1] = op_code;
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);
return buf;
}
/**
* 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;
void *buf;
void *data = NULL;
uint32_t maxcnt;
uint32_t size = 65536;
maxcnt = (size - diskinfo->bps) / diskinfo->bps;
if (!count || count > maxcnt || lba + count > diskinfo->lbacnt)
return NULL;
memset(&inreg, 0, sizeof inreg);
if (diskinfo->ebios)
buf = ebios_setup(diskinfo, &inreg, lba, count, EBIOS_READ_CODE);
else
buf = chs_setup(diskinfo, &inreg, lba, count, CHS_READ_CODE);
if (!buf)
return NULL;
if (disk_int13_retry(&inreg, NULL))
goto out;
data = malloc(count * diskinfo->bps);
if (data)
memcpy(data, buf, count * diskinfo->bps);
out:
lfree(buf);
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;
void *buf;
uint32_t maxcnt;
uint32_t size = 65536;
int rv = -1;
maxcnt = (size - diskinfo->bps) / diskinfo->bps;
if (!count || count > maxcnt || lba + count > diskinfo->lbacnt)
return -1;
memset(&inreg, 0, sizeof inreg);
if (diskinfo->ebios)
buf = ebios_setup(diskinfo, &inreg, lba, count, EBIOS_WRITE_CODE);
else
buf = chs_setup(diskinfo, &inreg, lba, count, CHS_WRITE_CODE);
if (!buf)
return -1;
memcpy(buf, data, count * diskinfo->bps);
if (disk_int13_retry(&inreg, NULL))
goto out;
rv = 0; /* ok */
out:
lfree(buf);
return rv;
}
/**
* 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);
}