diff mbox

AHCI read/write corruption with int13h

Message ID 1406679726-32293-1-git-send-email-eniacz@hp.com
State New
Headers show

Commit Message

Eniac Zhang July 30, 2014, 12:22 a.m. UTC
The AHCI controller code in Qemu has a bug that it will use the
 wrong LBA address when Seabios tries to access LBA>128GB
 (aka 127.5GB limit http://www.hardwaresecrets.com/printpage/Hard-Disk-Drives-Capacity-Limits/482).
 When we needs to access the LBA>0xfffffff, 28bit LBA is not sufficient
 thus AHCI code needs to convert that into an LBA48 command, but it
 didn’t set all the flags correctly, so low level code ends up reading a sector at different address.

how to duplicate:
turn off the workaround in ahci.c, leaving the debug logs in core.c,
 compile your qemu-system-x86_64 and then run:
./ qemu-system-x86_64 -fda dos622.img -drive if=none,file=./blank.qcow2,id=hdc,media=disk -device ide-hd,drive=hdc,bus=ide.0 -M q35 -m 256M -vnc :1 -boot a

Blank.qcow2 is a 300GB virtual disk file I pre-created, you can leave it blank cause what’s on disk doesn’t matter in this test.  dos622.img is the dos622 floppy image with debug.com and a batch file:
a 100
mov si, 0200
mov ax, 4200
mov dx, 0080
int 13
ret

; 0x3: length
e 200 10 00 7f 00 00 00 00 50
; lba
e 208 88 69 e2 11 00 00 00 00

r ip
100
g
r
d 5000:0
q

Connect vncviewer, once dos boot is completed, type debug<int1342.bat to try to read a sector beyond 128GB using int13h.
---
 hw/ide/ahci.c | 15 +++++++++++++++
 hw/ide/core.c | 20 ++++++++++++++++++++
 2 files changed, 35 insertions(+)
diff mbox

Patch

diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
index 604152a..3e86953 100644
--- a/hw/ide/ahci.c
+++ b/hw/ide/ahci.c
@@ -938,6 +938,21 @@  static int handle_cmd(AHCIState *s, int port, int slot)
              * do, I simply assume non-used fields as reserved and OR everything
              * together, independent of the command.
              */
+             // enable lba and lba48 mode, otherwise the bit won't get set until the command is completed, cause read/write corruption
+             ide_state->lba48 = (cmd_fis[2] == WIN_READDMA_EXT 
+ 		|| cmd_fis[2] == WIN_READ_EXT
+ 		|| cmd_fis[2] == WIN_READDMA_QUEUED_EXT
+ 		|| cmd_fis[2] == WIN_READ_NATIVE_MAX_EXT
+ 		|| cmd_fis[2] == WIN_MULTREAD_EXT
+ 		|| cmd_fis[2] == WIN_WRITE_EXT
+ 		|| cmd_fis[2] == WIN_WRITEDMA_EXT
+ 		|| cmd_fis[2] == WIN_WRITEDMA_QUEUED_EXT
+ 		|| cmd_fis[2] == WIN_SET_MAX_EXT
+ 		|| cmd_fis[2] == WIN_MULTWRITE_EXT
+ 		|| cmd_fis[2] == WIN_VERIFY_EXT
+ 		|| cmd_fis[2] == WIN_FLUSH_CACHE_EXT
+ 		);
+             ide_state->select |= 0x40;
             ide_set_sector(ide_state, ((uint64_t)cmd_fis[10] << 40)
                                     | ((uint64_t)cmd_fis[9] << 32)
                                     /* This is used for LBA48 commands */
diff --git a/hw/ide/core.c b/hw/ide/core.c
index db191a6..988a935 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -445,9 +445,23 @@  void ide_transfer_stop(IDEState *s)
     s->status &= ~DRQ_STAT;
 }
 
+#define DEBUG_SECTOR 1
+
+#if DEBUG_SECTOR
+#define DPRINTF(fmt, ...)                                       \
+    do { printf("debug_sector: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+/* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables.  */
+#define ACPI_DATA_SIZE       0x10000
+#define BIOS_CFG_IOPORT 0x510
+
 int64_t ide_get_sector(IDEState *s)
 {
     int64_t sector_num;
+
     if (s->select & 0x40) {
         /* lba */
 	if (!s->lba48) {
@@ -464,12 +478,18 @@  int64_t ide_get_sector(IDEState *s)
         sector_num = ((s->hcyl << 8) | s->lcyl) * s->heads * s->sectors +
             (s->select & 0x0f) * s->sectors + (s->sector - 1);
     }
+#if DEBUG_SECTOR
+    DPRINTF("get_sector: %lx\n", sector_num);
+#endif
     return sector_num;
 }
 
 void ide_set_sector(IDEState *s, int64_t sector_num)
 {
     unsigned int cyl, r;
+#if DEBUG_SECTOR
+    DPRINTF("set_sector: %lx\n", sector_num);
+#endif
     if (s->select & 0x40) {
 	if (!s->lba48) {
             s->select = (s->select & 0xf0) | (sector_num >> 24);