summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@linux.intel.com>2010-07-16 16:18:16 -0700
committerH. Peter Anvin <hpa@linux.intel.com>2010-07-16 16:19:22 -0700
commit22c96d213d282f715043770bf347a99c81299e8f (patch)
treef4c824c96e4d020fca225f041ee30d1ae64e6d85 /core
parent6da4f4685e9842bac8051f2b6249d29544a1ebe7 (diff)
downloadsyslinux-22c96d213d282f715043770bf347a99c81299e8f.tar.gz
diskio: allow fallback from EDD to CHS
At least one system has been found which require fallback from EDD to CHS, so actually make it doable. Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
Diffstat (limited to 'core')
-rw-r--r--core/fs/diskio.c111
1 files changed, 72 insertions, 39 deletions
diff --git a/core/fs/diskio.c b/core/fs/diskio.c
index d4901c3d..746335f1 100644
--- a/core/fs/diskio.c
+++ b/core/fs/diskio.c
@@ -23,6 +23,10 @@ static int chs_rdwr_sectors(struct disk *disk, void *buf,
size_t done = 0;
size_t bytes;
int retry;
+ uint32_t maxtransfer = disk->maxtransfer;
+
+ if (maxtransfer > 63)
+ maxtransfer = 63;
memset(&ireg, 0, sizeof ireg);
@@ -31,8 +35,8 @@ static int chs_rdwr_sectors(struct disk *disk, void *buf,
while (count) {
chunk = count;
- if (chunk > disk->maxtransfer)
- chunk = disk->maxtransfer;
+ if (chunk > maxtransfer)
+ chunk = maxtransfer;
freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift;
@@ -47,16 +51,19 @@ static int chs_rdwr_sectors(struct disk *disk, void *buf,
if (chunk > freeseg)
chunk = freeseg;
- bytes = chunk << sector_shift;
-
- if (tptr != ptr && is_write)
- memcpy(tptr, ptr, bytes);
-
s = xlba % disk->s;
t = xlba / disk->s;
h = t % disk->h;
c = t / disk->h;
+ if (chunk > (disk->s - s))
+ chunk = disk->s - s;
+
+ bytes = chunk << sector_shift;
+
+ if (tptr != ptr && is_write)
+ memcpy(tptr, ptr, bytes);
+
ireg.eax.b[0] = chunk;
ireg.ecx.b[1] = c;
ireg.ecx.b[0] = ((c & 0x300) >> 2) | (s+1);
@@ -67,34 +74,39 @@ static int chs_rdwr_sectors(struct disk *disk, void *buf,
retry = RETRY_COUNT;
for (;;) {
- dprintf("CHS[%02x]: %u @ %llu (%u/%u/%u) %04x:%04x %s %p\n",
- ireg.edx.b[0], chunk, xlba, c, h, s+1,
- ireg.es, ireg.ebx.w[0],
- (ireg.eax.b[1] & 1) ? "<-" : "->",
- ptr);
-
- __intcall(0x13, &ireg, &oreg);
- if (!(oreg.eflags.l & EFLAGS_CF))
- break;
-
- dprintf("CHS: error AX = %04x\n", oreg.eax.w[0]);
-
- if (retry--)
- continue;
-
- /* For any starting value, this will always end with ..., 1, 0 */
- chunk >>= 1;
- if (chunk) {
- disk->maxtransfer = chunk;
- retry = RETRY_COUNT;
- ireg.eax.b[0] = chunk;
- continue;
- } else {
- printf("CHS: Error %04x %s sector %llu (%u/%u/%u)\n",
- oreg.eax.w[0],
- is_write ? "writing" : "reading",
- lba, c, h, s+1);
+ if (c < 1024) {
+ dprintf("CHS[%02x]: %u @ %llu (%u/%u/%u) %04x:%04x %s %p\n",
+ ireg.edx.b[0], chunk, xlba, c, h, s+1,
+ ireg.es, ireg.ebx.w[0],
+ (ireg.eax.b[1] & 1) ? "<-" : "->",
+ ptr);
+
+ __intcall(0x13, &ireg, &oreg);
+ if (!(oreg.eflags.l & EFLAGS_CF))
+ break;
+
+ dprintf("CHS: error AX = %04x\n", oreg.eax.w[0]);
+
+ if (retry--)
+ continue;
+
+ /*
+ * For any starting value, this will always end with
+ * ..., 1, 0
+ */
+ chunk >>= 1;
+ if (chunk) {
+ maxtransfer = chunk;
+ retry = RETRY_COUNT;
+ ireg.eax.b[0] = chunk;
+ continue;
+ }
}
+
+ printf("CHS: Error %04x %s sector %llu (%u/%u/%u)\n",
+ oreg.eax.w[0],
+ is_write ? "writing" : "reading",
+ lba, c, h, s+1);
return done; /* Failure */
}
@@ -103,6 +115,9 @@ static int chs_rdwr_sectors(struct disk *disk, void *buf,
if (tptr != ptr && !is_write)
memcpy(ptr, tptr, bytes);
+ /* If we dropped maxtransfer, it eventually worked, so remember it */
+ disk->maxtransfer = maxtransfer;
+
ptr += bytes;
xlba += chunk;
count -= chunk;
@@ -131,6 +146,7 @@ static int edd_rdwr_sectors(struct disk *disk, void *buf,
size_t done = 0;
size_t bytes;
int retry;
+ uint32_t maxtransfer = disk->maxtransfer;
memset(&ireg, 0, sizeof ireg);
@@ -146,8 +162,8 @@ static int edd_rdwr_sectors(struct disk *disk, void *buf,
lba += disk->part_start;
while (count) {
chunk = count;
- if (chunk > disk->maxtransfer)
- chunk = disk->maxtransfer;
+ if (chunk > maxtransfer)
+ chunk = maxtransfer;
freeseg = (0x10000 - ((size_t)ptr & 0xffff)) >> sector_shift;
@@ -201,13 +217,26 @@ static int edd_rdwr_sectors(struct disk *disk, void *buf,
/* For any starting value, this will always end with ..., 1, 0 */
chunk >>= 1;
if (chunk) {
- disk->maxtransfer = chunk;
+ maxtransfer = chunk;
retry = RETRY_COUNT;
continue;
}
- /*** XXX: Consider falling back to CHS here?! ***/
- printf("EDD: Error %04x %s sector %llu\n",
+ /*
+ * Total failure. There are systems which identify as
+ * EDD-capable but aren't; the known such systems return
+ * error code AH=1 (invalid function), but let's not
+ * assume that for now.
+ */
+ if (lba < ((disk->h * disk->s) << 10)) {
+ done = chs_rdwr_sectors(disk, buf, lba, count, is_write);
+ if (done == (count << sector_shift)) {
+ /* Successful, assume this is a CHS disk */
+ disk->rdwr_sectors = chs_rdwr_sectors;
+ return done;
+ }
+ }
+ printf("EDD: Error %04x %s sector %llu\n",
oreg.eax.w[0],
is_write ? "writing" : "reading",
lba);
@@ -219,6 +248,9 @@ static int edd_rdwr_sectors(struct disk *disk, void *buf,
if (tptr != ptr && !is_write)
memcpy(ptr, tptr, bytes);
+ /* If we dropped maxtransfer, it eventually worked, so remember it */
+ disk->maxtransfer = maxtransfer;
+
ptr += bytes;
lba += chunk;
count -= chunk;
@@ -226,6 +258,7 @@ static int edd_rdwr_sectors(struct disk *disk, void *buf,
}
return done;
}
+
struct edd_disk_params {
uint16_t len;
uint16_t flags;