blob: b184c6600bd7693b944a0ae5e2bcdff954cc66d8 [file] [log] [blame] [raw]
"use strict";
var
/** @const */
CDROM_SECTOR_SIZE = 2048,
/** @const */
HD_SECTOR_SIZE = 512;
/** @constructor */
function IDEDevice(cpu, buffer, is_cd, nr)
{
var pic = cpu.devices.pic,
memory = cpu.memory,
me = this;
// gets set via PCI in seabios, likely doesn't matter
if(nr === 0)
{
this.ata_port = 0x1F0;
this.irq = 14;
this.pci_id = 0x1E << 3;
}
else
{
this.ata_port = 0x1F0;
this.irq = 14;
this.pci_id = 0x1F << 3;
}
// alternate status, starting at 3f4/374
this.ata_port_high = this.ata_port | 0x204;
this.master_port = 0xC000;
this.io = cpu.io;
this.pic = cpu.devices.pic;
this.pci = cpu.devices.pci;
this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE;
this.buffer = buffer;
this.is_atapi = is_cd;
if(buffer)
{
this.sector_count = me.buffer.byteLength / this.sector_size;
if(this.sector_size !== (this.sector_size | 0))
{
dbg_log("Warning: Disk size not aligned with sector size", LOG_DISK);
this.sector_count = Math.ceil(this.sector_count);
}
if(is_cd)
{
this.head_count = 1;
this.sectors_per_track = 0;
}
else
{
this.head_count = 255;
this.sectors_per_track = 63;
}
this.cylinder_count = this.sector_count / (this.head_count + 1) / (this.sectors_per_track + 1);
if(this.cylinder_count !== (this.cylinder_count | 0))
{
dbg_log("Warning: Rounding up cylinder count. Choose different head number", LOG_DISK);
this.cylinder_count = Math.ceil(this.cylinder_count);
}
}
else
{
this.sector_count = 0;
this.head_count = 0;
this.sectors_per_track = 0;
this.cylinder_count = 0;
}
this.stats = {
sectors_read: 0,
sectors_written: 0,
bytes_read: 0,
bytes_written: 0,
};
function push_irq()
{
if((device_control & 2) === 0)
{
dbg_log("push irq", LOG_DISK);
dma_status |= 4;
pic.push_irq(me.irq);
}
}
this.pci_space = [
0x86, 0x80, 0x20, 0x3a, 0x05, 0x00, 0xa0, 0x02, 0x00, 0x8f, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
this.ata_port & 0xFF | 1, this.ata_port >> 8, 0x00, 0x00,
this.ata_port_high & 0xFF | 1, this.ata_port_high >> 8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
this.master_port & 0xFF | 1, this.master_port >> 8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x43, 0x10, 0xd4, 0x82,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, this.irq, 0x01, 0x00, 0x00,
// 0x40
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x80
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
this.pci_bars = [
{
size: 8,
},
{
size: 4,
},
false,
false,
{
size: 0x10,
},
];
// 00:1f.2 IDE interface: Intel Corporation 82801JI (ICH10 Family) 4 port SATA IDE Controller #1
//0x86, 0x80, 0x20, 0x3a, 0x05, 0x00, 0xb0, 0x02, 0x00, 0x8f, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00,
//0x01, 0x90, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x81, 0x88, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00,
//0x81, 0x84, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x10, 0xd4, 0x82,
//0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00,
cpu.devices.pci.register_device(this);
// status
this.io.register_read(this.ata_port | 7, read_status);
this.io.register_read(this.ata_port_high | 2, read_status);
this.io.register_write(this.ata_port | 7, write_control);
this.io.register_write(this.ata_port_high | 2, write_control);
var device_control = 2,
last_drive = 0xFF,
data_pointer = 0,
pio_data = new Uint8Array(0),
is_lba = 0,
slave = 0,
bytecount = 0,
sector = 0,
lba_count = 0,
cylinder_low = 0,
cylinder_high = 0,
head = 0,
drive_head = 0,
status = 0x50,
sectors_per_drq = 1,
write_dest,
data_port_count = 0,
data_port_current = 0,
data_port_buffer = [],
data_port_callback;
function atapi_handle()
{
dbg_log("ATAPI Command: " + h(data_port_buffer[0]), LOG_DISK);
bytecount = 2;
switch(data_port_buffer[0])
{
case 0x00:
// test unit ready
status = 0x40;
cylinder_low = 8;
cylinder_high = 0;
push_irq();
break;
case 0x03:
// request sense
pio_data = new Uint8Array(Math.min(data_port_buffer[4], 15));
status = 0x58;
pio_data[0] = 0x80 | 0x70;
pio_data[7] = 8;
data_pointer = 0;
bytecount = 2;
cylinder_low = 8;
cylinder_high = 0;
push_irq();
break;
case 0x12:
// inquiry
pio_data = new Uint8Array(Math.min(data_port_buffer[4], 36));
status = 0x58;
// http://www.t10.org/ftp/x3t9.2/document.87/87-106r0.txt
pio_data.set([
0x05, 0x80, 0x01, 0x31,
0, 0, 0, 0,
// 8
0x53, 0x4F, 0x4E, 0x59,
0x20, 0x20, 0x20, 0x20,
// 16
0x43, 0x44, 0x2D, 0x52,
0x4F, 0x4D, 0x20, 0x43,
0x44, 0x55, 0x2D, 0x31,
0x30, 0x30, 0x30, 0x20,
// 32
0x31, 0x2E, 0x31, 0x61,
]);
data_pointer = 0;
bytecount = 2;
push_irq();
break;
case 0x1E:
// prevent/allow medium removal
pio_data = new Uint8Array(0);
status = 0x50;
data_pointer = 0;
bytecount = 2;
push_irq();
break;
case 0x25:
// read capacity
pio_data = new Uint8Array([
me.sector_count >> 24 & 0xff,
me.sector_count >> 16 & 0xff,
me.sector_count >> 8 & 0xff,
me.sector_count & 0xff,
0,
0,
me.sector_size >> 8 & 0xff,
me.sector_size & 0xff,
]);
status = 0x58;
data_pointer = 0;
bytecount = 2;
cylinder_low = 8;
cylinder_high = 0;
push_irq();
break;
case 0x28:
// read
if(lba_count & 1)
{
atapi_read_dma(data_port_buffer);
}
else
{
atapi_read(data_port_buffer);
}
break;
case 0x43:
// read header
pio_data = new Uint8Array(2048);
pio_data[0] = 0;
pio_data[1] = 10;
pio_data[2] = 1;
pio_data[3] = 1;
status = 0x58;
data_pointer = 0;
bytecount = 2;
cylinder_high = 8;
cylinder_low = 0;
push_irq();
break;
case 0x46:
// get configuration
pio_data = new Uint8Array(data_port_buffer[8] | data_port_buffer[7] << 8);
status = 0x58;
data_pointer = 0;
bytecount = 2;
push_irq();
break;
case 0x4A:
// get event status notification
pio_data = new Uint8Array(data_port_buffer[8] | data_port_buffer[7] << 8);
status = 0x58;
data_pointer = 0;
bytecount = 2;
push_irq();
break;
case 0x51:
// read disk information
pio_data = new Uint8Array(0);
status = 0x50;
data_pointer = 0;
bytecount = 2;
push_irq();
break;
case 0x5A:
// mode sense
push_irq();
status = 0x50;
break;
default:
status = 0x50;
dbg_log("Unimplemented ATAPI command: " + h(data_port_buffer[0]), LOG_DISK);
}
}
function do_write()
{
status = 0x50;
var data = data_port_buffer.subarray(0, data_port_count);
me.buffer.set(write_dest, data, function()
{
push_irq();
});
me.stats.sectors_written += data_port_count / me.sector_size | 0;
me.stats.bytes_written += data_port_count;
}
var next_status = -1;
function read_status()
{
var ret = status;
dbg_log("ATA read status: " + h(status), LOG_DISK);
if(next_status >= 0)
{
status = next_status;
next_status = -1;
}
return ret;
}
function write_control(data, port)
{
dbg_log("device control: " + h(data) + " port=" + h(port), LOG_DISK);
device_control = data;
if(data & 4)
{
dbg_log("Reset via control port", LOG_DISK);
// reset
if(me.is_atapi)
{
status = 0x50 | 1;
bytecount = 1;
lba_count = 1;
sector = 1; // lba_low
cylinder_low = 0x14; // lba_mid
cylinder_high = 0xeb; // lba_high
}
else
{
status = 0x50 | 1;
bytecount = 1;
lba_count = 1;
sector = 1; // lba_low
cylinder_low = 0x3c; // lba_mid
cylinder_high = 0xc3; // lba_high
}
}
}
function allocate_in_buffer(size)
{
// reuse old buffer if it's smaller or the same size
if(size > data_port_buffer.length)
{
data_port_buffer = new Uint8Array(size);
}
data_port_count = size;
data_port_current = 0;
}
function atapi_read(cmd)
{
// Note: Big Endian
var lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5],
count = cmd[7] << 8 | cmd[8],
flags = cmd[1],
byte_count = count * me.sector_size,
//transfered_ata_blocks = Math.min(bytecount / 512, cylinder_low << 8 | cylinder_high),
max_drq_size = (cylinder_high & 0xFF) << 8 | cylinder_low & 0xFF,
transfered_ata_blocks,
start = lba * me.sector_size;
dbg_log("CD read lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count) +
" flags=" + h(flags), LOG_CD);
if(!max_drq_size)
{
max_drq_size = 0x8000;
}
transfered_ata_blocks = Math.min(byte_count, max_drq_size);
cylinder_low = transfered_ata_blocks & 0xFF;
cylinder_high = transfered_ata_blocks >> 8 & 0xFF;
if(start >= buffer.byteLength)
{
dbg_log("CD read: Outside of disk end=" + h(start + byte_count) +
" size=" + h(buffer.byteLength), LOG_DISK);
status = 0xFF;
push_irq();
}
else
{
byte_count = Math.min(byte_count, buffer.byteLength - start);
status = 0x80;
me.buffer.get(start, byte_count, function(data)
{
pio_data = data;
status = 0x58;
//cylinder_low = 0;
//cylinder_high = 8;
data_pointer = 0;
push_irq();
me.stats.sectors_read += byte_count / me.sector_size | 0;
me.stats.bytes_read += byte_count;
});
}
}
function atapi_read_dma(cmd)
{
// Note: Big Endian
var lba = cmd[2] << 24 | cmd[3] << 16 | cmd[4] << 8 | cmd[5],
count = cmd[7] << 8 | cmd[8],
flags = cmd[1],
byte_count = count * me.sector_size,
start = lba * me.sector_size;
dbg_log("CD read DMA lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count) +
" flags=" + h(flags), LOG_CD);
if(start >= buffer.byteLength)
{
dbg_log("CD read: Outside of disk end=" + h(start + byte_count) +
" size=" + h(buffer.byteLength), LOG_DISK);
status = 0xFF;
push_irq();
}
else
{
byte_count = Math.min(byte_count, buffer.byteLength - start);
status = 0x80;
me.buffer.get(start, byte_count, function(data)
{
var prdt_start = prdt_addr,
offset = 0;
do {
var addr = memory.read32s(prdt_start),
count = memory.read16(prdt_start + 4),
end = memory.read8(prdt_start + 7) & 0x80;
if(!count)
{
count = 0x10000;
}
dbg_log("dma read dest=" + h(addr) + " count=" + h(count), LOG_DISK);
memory.write_blob(data.subarray(offset, offset + count), addr);
offset += count;
prdt_start += 8;
}
while(!end);
status = 0x50;
dma_status &= ~2 & ~1;
dma_status |= 4;
push_irq();
me.stats.sectors_read += byte_count / me.sector_size | 0;
me.stats.bytes_read += byte_count;
});
}
}
function read_data_port(port_addr)
{
if(port_addr === me.ata_port)
{
return read_data();
}
else if(port_addr === (me.ata_port | 1))
{
dbg_log("Read lba_count: " + h(lba_count & 0xFF), LOG_DISK);
return lba_count & 0xFF;
}
else if(port_addr === (me.ata_port | 2))
{
dbg_log("Read bytecount: " + h(bytecount & 0xFF), LOG_DISK);
return bytecount & 0xFF;
}
else if(port_addr === (me.ata_port | 3))
{
dbg_log("Read sector: " + h(sector & 0xFF), LOG_DISK);
return sector & 0xFF;
}
}
function read_data()
{
if(data_pointer < pio_data.length)
{
if((data_pointer + 1) % (sectors_per_drq * 512) === 0 ||
data_pointer + 1 === pio_data.length)
{
dbg_log("ATA IRQ", LOG_DISK);
push_irq();
}
if(cylinder_low)
{
cylinder_low--;
}
else
{
if(cylinder_high)
{
cylinder_high--;
cylinder_low = 0xFF;
}
}
if(!cylinder_low && !cylinder_high)
{
var remaining = pio_data.length - data_pointer - 1;
dbg_log("reset to " + h(remaining), LOG_DISK);
if(remaining >= 0x10000)
{
cylinder_high = 0xF0;
cylinder_low = 0;
}
else
{
cylinder_high = remaining >> 8;
cylinder_low = remaining;
}
}
if(data_pointer + 1 >= pio_data.length)
{
status = 0x50;
//bytecount = 3;
}
if((data_pointer + 1 & 255) === 0)
{
dbg_log("Read 1F0: " + h(pio_data[data_pointer], 2) +
" cur=" + h(data_pointer) +
" cnt=" + h(pio_data.length), LOG_DISK);
}
return pio_data[data_pointer++];
}
else
{
dbg_log("Read 1F0: empty", LOG_DISK);
data_pointer++;
return 0;
}
}
this.io.register_read(me.ata_port | 0, read_data_port);
this.io.register_read(me.ata_port | 1, read_data_port);
this.io.register_read(me.ata_port | 2, read_data_port);
this.io.register_read(me.ata_port | 3, read_data_port);
this.io.register_read(me.ata_port | 4, function()
{
dbg_log("Read 1F4: " + h(cylinder_low & 0xFF), LOG_DISK);
return cylinder_low & 0xFF;
});
this.io.register_read(me.ata_port | 5, function(port)
{
dbg_log("Read 1F5: " + h(cylinder_high & 0xFF), LOG_DISK);
return cylinder_high & 0xFF;
});
this.io.register_read(me.ata_port | 6, function()
{
dbg_log("Read 1F6", LOG_DISK);
return drive_head;
});
function write_data_port(data, port_addr)
{
if(port_addr === me.ata_port)
{
if(data_port_current >= data_port_count)
{
dbg_log("Redundant write to data port: " + h(data) + " count=" + h(data_port_count) +
" cur=" + h(data_port_current), LOG_DISK);
}
else
{
if((data_port_current + 1 & 255) === 0 || data_port_count < 20)
{
dbg_log("Data port: " + h(data) + " count=" + h(data_port_count) +
" cur=" + h(data_port_current), LOG_DISK);
}
data_port_buffer[data_port_current++] = data;
if((data_port_current % (sectors_per_drq * 512)) === 0)
{
dbg_log("ATA IRQ", LOG_DISK);
push_irq();
}
if(data_port_current === data_port_count)
{
data_port_callback();
}
}
}
else if(port_addr === (me.ata_port | 1))
{
dbg_log("1F1/lba_count: " + h(data), LOG_DISK);
lba_count = (lba_count << 8 | data) & 0xFFFF;
}
else if(port_addr === (me.ata_port | 2))
{
dbg_log("1F2/bytecount: " + h(data), LOG_DISK);
bytecount = (bytecount << 8 | data) & 0xFFFF;
}
else if(port_addr === (me.ata_port | 3))
{
dbg_log("1F3/sector: " + h(data), LOG_DISK);
sector = (sector << 8 | data) & 0xFFFF;
}
}
this.io.register_write(me.ata_port | 0, write_data_port);
this.io.register_write(me.ata_port | 1, write_data_port);
this.io.register_write(me.ata_port | 2, write_data_port);
this.io.register_write(me.ata_port | 3, write_data_port);
this.io.register_write(me.ata_port | 4, function(data)
{
dbg_log("1F4/sector low: " + h(data), LOG_DISK);
cylinder_low = (cylinder_low << 8 | data) & 0xFFFF;
});
this.io.register_write(me.ata_port | 5, function(data)
{
dbg_log("1F5/sector high: " + h(data), LOG_DISK);
cylinder_high = (cylinder_high << 8 | data) & 0xFFFF;
});
this.io.register_write(me.ata_port | 6, function(data)
{
var slave = data & 0x10,
mode = data & 0xE0,
low = data & 0xF;
dbg_log("1F6/drive: " + h(data, 2), LOG_DISK);
if(slave)
{
dbg_log("Slave", LOG_DISK);
return;
}
if((mode & 0x40) === 0)
{
// chs mode
//dbg_log("CHS mode: Unimplemented", LOG_DISK);
//return;
}
drive_head = data;
is_lba = data >> 6 & 1;
head = data & 0xF;
last_drive = data;
});
this.io.register_write(me.ata_port | 7, function(cmd)
{
dbg_log("ATA Command: " + h(cmd), LOG_DISK);
switch(cmd)
{
case 0x00:
// NOP
push_irq();
status = 0x50;
break;
case 0x08:
dbg_log("ATA device reset", LOG_DISK);
data_pointer = 0;
pio_data = new Uint8Array(0);
status = 0x50;
push_irq();
break;
case 0x10:
// obsolete
dbg_log("ATA cmd 10", LOG_DISK);
push_irq();
break;
case 0x27:
// read native max address ext - read the actual size of the HD
// https://en.wikipedia.org/wiki/Host_protected_area
dbg_log("ATA cmd 27", LOG_DISK);
push_irq();
pio_data = new Uint8Array([
0, 0, // error
0, 0, // count
// result
me.buffer.byteLength & 0xff,
me.buffer.byteLength >> 8 & 0xff,
me.buffer.byteLength >> 16 & 0xff,
me.buffer.byteLength >> 24 & 0xff,
0, 0,
0, 0,
]);
status = 0x58;
break;
case 0x20:
case 0x29:
case 0x24:
case 0xC4:
// 0x20 read sectors
// 0x24 read sectors ext
// 0xC4 read multiple
// 0x29 read multiple ext
ata_read_sectors(cmd);
break;
case 0x30:
case 0x34:
case 0x39:
// 0x30 write sectors
// 0x34 write sectors ext
// 0x39 write multiple ext
ata_write(cmd);
break;
case 0x90:
// EXECUTE DEVICE DIAGNOSTIC
dbg_log("ATA cmd 90", LOG_DISK);
push_irq();
lba_count = 0x101;
status = 0x50;
break;
case 0x91:
// INITIALIZE DEVICE PARAMETERS
dbg_log("ATA cmd 91", LOG_DISK);
push_irq();
break;
case 0xA0:
if(me.is_atapi)
{
// ATA_CMD_PACKET
status = 0x58;
allocate_in_buffer(12);
data_port_callback = atapi_handle;
bytecount = 1;
push_irq();
}
break;
case 0xA1:
dbg_log("ATA identify packet device", LOG_DISK);
if(me.is_atapi)
{
create_identify_packet();
status = 0x58;
push_irq();
}
else
{
status = 0x50;
push_irq();
}
break;
case 0xC6:
// SET MULTIPLE MODE
dbg_log("ATA cmd C6", LOG_DISK);
// Logical sectors per DRQ Block in word 1
dbg_log("Logical sectors per DRQ Block: " + h(bytecount), LOG_DISK);
sectors_per_drq = bytecount;
push_irq();
break;
case 0xC8:
// 0xC8 read dma
ata_read_sectors_dma(cmd);
break;
case 0xCA:
// write dma
ata_write_dma(cmd);
break;
case 0xE1:
dbg_log("ATA idle immediate", LOG_DISK);
push_irq();
break;
case 0xEC:
dbg_log("ATA identify device", LOG_DISK);
// identify device
if(me.is_atapi)
{
return;
}
create_identify_packet();
status = 0x58;
push_irq();
break;
case 0xEA:
// FLUSH CACHE EXT
dbg_log("ATA cmd EA", LOG_DISK);
push_irq();
break;
case 0xEF:
// SET FEATURES
dbg_log("ATA cmd EF", LOG_DISK);
push_irq();
break;
default:
dbg_log("New ATA cmd on 1F7: " + h(cmd), LOG_DISK);
// abort bit set
lba_count = 4;
}
});
function ata_read_sectors(cmd)
{
if(cmd === 0x20 || cmd === 0xC4)
{
// read sectors
var count = bytecount & 0xff,
lba = is_lba ? get_lba28() : get_chs();
if(count === 0)
count = 0x100;
}
else if(cmd === 0x24 || cmd === 0x29)
{
// read sectors ext
var count = bytecount,
lba = get_lba48();
if(count === 0)
count = 0x10000;
}
else
{
dbg_assert(false);
}
var
byte_count = count * me.sector_size,
start = lba * me.sector_size;
dbg_log("ATA read lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count), LOG_DISK);
cylinder_low += count;
if(start + byte_count > buffer.byteLength)
{
dbg_log("ATA read: Outside of disk", LOG_DISK);
status = 0xFF;
push_irq();
}
else
{
//status = 0xFF & ~8;
status = 0x80;
me.buffer.get(start, byte_count, function(data)
{
pio_data = data;
status = 0x58;
data_pointer = 0;
push_irq();
me.stats.sectors_read += byte_count / me.sector_size | 0;
me.stats.bytes_read += byte_count;
});
}
}
function ata_read_sectors_dma(cmd)
{
var count = bytecount & 0xff,
lba = get_lba28();
var byte_count = count * me.sector_size,
start = lba * me.sector_size;
dbg_log("ATA DMA read lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count), LOG_DISK);
cylinder_low += count;
if(start + byte_count > buffer.byteLength)
{
dbg_log("ATA read: Outside of disk", LOG_DISK);
status = 0xFF;
push_irq();
return;
}
//status = 0xFF & ~8;
status = 0x80;
dma_status |= 1;
me.buffer.get(start, byte_count, function(data)
{
var prdt_start = prdt_addr,
offset = 0;
do {
var addr = memory.read32s(prdt_start),
count = memory.read16(prdt_start + 4),
end = memory.read8(prdt_start + 7) & 0x80;
if(!count)
{
count = 0x10000;
}
dbg_log("dma read dest=" + h(addr) + " count=" + h(count), LOG_DISK);
memory.write_blob(data.subarray(offset, offset + count), addr);
offset += count;
prdt_start += 8;
}
while(!end);
status = 0x50;
dma_status &= ~2 & ~1;
dma_status |= 4;
push_irq();
me.stats.sectors_read += byte_count / me.sector_size | 0;
me.stats.bytes_read += byte_count;
});
}
function ata_write(cmd)
{
if(cmd === 0x30)
{
// write sectors
var count = bytecount & 0xff,
lba = is_lba ? get_lba28() : get_chs();
if(count === 0)
count = 0x100;
}
else if(cmd === 0x34 || cmd === 0x39)
{
var count = bytecount,
lba = get_lba48();
if(count === 0)
count = 0x10000;
}
else
{
dbg_assert(false);
}
var byte_count = count * me.sector_size,
start = lba * me.sector_size;
dbg_log("ATA write lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count), LOG_DISK);
cylinder_low += count;
if(start + byte_count > buffer.byteLength)
{
dbg_log("ATA write: Outside of disk", LOG_DISK);
status = 0xFF;
push_irq();
}
else
{
status = 0x50;
next_status = 0x58;
allocate_in_buffer(byte_count);
write_dest = start;
data_port_callback = do_write;
//bytecount = 1;
push_irq();
}
}
function ata_write_dma(cmd)
{
var count = bytecount & 0xff,
lba = get_lba28();
var byte_count = count * me.sector_size,
start = lba * me.sector_size;
dbg_log("ATA DMA write lba=" + h(lba) +
" lbacount=" + h(count) +
" bytecount=" + h(byte_count), LOG_DISK);
cylinder_low += count;
if(start + byte_count > buffer.byteLength)
{
dbg_log("ATA DMA write: Outside of disk", LOG_DISK);
status = 0xFF;
push_irq();
return;
}
//status = 0xFF & ~8;
status = 0x80;
dma_status |= 1;
var prdt_start = prdt_addr,
prdt_count = 0,
prdt_write_count = 0,
offset = 0;
do {
var prd_addr = memory.read32s(prdt_start),
prd_count = memory.read16(prdt_start + 4),
end = memory.read8(prdt_start + 7) & 0x80;
if(!prd_count)
{
prd_count = 0x10000;
}
dbg_log("dma write dest=" + h(prd_addr) + " prd_count=" + h(prd_count), LOG_DISK);
var slice = memory.mem8.subarray(prd_addr, prd_addr + prd_count);
me.buffer.set(start + offset, slice, function()
{
prdt_write_count++;
if(prdt_write_count === prdt_count)
{
dbg_log("dma write completed", LOG_DISK);
status = 0x50;
push_irq();
dma_status &= ~2 & ~1;
dma_status |= 4;
}
});
offset += prd_count;
prdt_start += 8;
prdt_count++;
}
while(!end);
if(prdt_write_count === prdt_count)
{
dbg_log("dma write completed", LOG_DISK);
status = 0x50;
push_irq();
dma_status &= ~2 & ~1;
dma_status |= 4;
}
me.stats.sectors_written += byte_count / me.sector_size | 0;
me.stats.bytes_written += byte_count;
}
function get_chs()
{
var c = cylinder_low & 0xFF | cylinder_high << 8 & 0xFF00,
h = head,
s = sector & 0xFF;
return (c * me.head_count + h) * me.sectors_per_track + s - 1;
}
function get_lba28()
{
return sector & 0xFF | cylinder_low << 8 & 0xFF00 | cylinder_high << 16 & 0xFF0000;
}
function get_lba48()
{
// Note: Bits over 32 missing
return (sector & 0xFF | cylinder_low << 8 & 0xFF00 | cylinder_high << 16 & 0xFF0000 |
(sector >> 8) << 24 & 0xFF000000) >>> 0;
}
function create_identify_packet()
{
data_pointer = 0;
// http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821
if(drive_head & 0x10)
{
// slave
pio_data = new Uint8Array(0);
return;
}
pio_data = new Uint8Array([
0x40, me.is_atapi ? 0x85 : 0,
// 1 cylinders
me.cylinder_count, me.cylinder_count >> 8,
0, 0,
// 3 heads
me.head_count, me.head_count >> 8,
0, 0,
// 5
0, 0,
// sectors per track
me.sectors_per_track, 0,
0, 0, 0, 0, 0, 0,
// 10-19 serial number
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 20
3, 0,
0, 2,
4, 0,
// 23-26 firmware revision
0, 0, 0, 0, 0, 0, 0, 0,
// 27 model number
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
// 47
0xFF, 0,
1, 0,
0, 3, // capabilities, 2: Only LBA / 3: LBA and DMA
// 50
0, 0,
0, 2,
0, 2,
7, 0,
// 54 cylinders
me.cylinder_count, me.cylinder_count >> 8,
// 55 heads
me.head_count, me.head_count >> 8,
// 56 sectors per track
me.sectors_per_track, 0,
// capacity in sectors
me.sector_count & 0xFF, me.sector_count >> 8 & 0xFF,
me.sector_count >> 16 & 0xFF, me.sector_count >> 24 & 0xFF,
0, 0,
// 60
me.sector_count & 0xFF, me.sector_count >> 8 & 0xFF,
me.sector_count >> 16 & 0xFF, me.sector_count >> 24 & 0xFF,
0, 0,
// 63, dma selected mode
0, 4,
//0, 0, // no DMA
0, 0,
// 65
30, 0, 30, 0, 30, 0, 30, 0, 0, 0,
// 70
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 75
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 80
0x7E, 0, 0, 0, 0, 0, 0, 0x74, 0, 0x40,
// 85
0, 0x40, 0, 0x74, 0, 0x40, 0, 0, 0, 0,
// 90
0, 0, 0, 0, 0, 0, 1, 0x60, 0, 0,
// 95
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// 100
me.sector_count & 0xFF, me.sector_count >> 8 & 0xFF,
me.sector_count >> 16 & 0xFF, me.sector_count >> 24 & 0xFF,
]);
if(me.cylinder_count > 16383)
{
pio_data[2] = pio_data[108] = 16383 & 0xFF;
pio_data[3] = pio_data[109] = 16383 >> 8;
}
}
var prdt_addr = 0,
dma_status;
this.io.register_read(this.master_port | 4, dma_read_addr0);
this.io.register_read(this.master_port | 5, dma_read_addr1);
this.io.register_read(this.master_port | 6, dma_read_addr2);
this.io.register_read(this.master_port | 7, dma_read_addr3);
this.io.register_write(this.master_port | 4, dma_set_addr0);
this.io.register_write(this.master_port | 5, dma_set_addr1);
this.io.register_write(this.master_port | 6, dma_set_addr2);
this.io.register_write(this.master_port | 7, dma_set_addr3);
function dma_read_addr0()
{
return prdt_addr & 0xFF;
}
function dma_read_addr1()
{
return prdt_addr >> 8 & 0xFF;
}
function dma_read_addr2()
{
return prdt_addr >> 16 & 0xFF;
}
function dma_read_addr3()
{
return prdt_addr >> 24 & 0xFF;
}
function dma_set_addr0(data)
{
prdt_addr = prdt_addr & ~0xFF | data;
}
function dma_set_addr1(data)
{
prdt_addr = prdt_addr & ~0xFF00 | data << 8;
}
function dma_set_addr2(data)
{
prdt_addr = prdt_addr & ~0xFF0000 | data << 16;
}
function dma_set_addr3(data)
{
prdt_addr = prdt_addr & 0xFFFFFF | data << 24;
dbg_log("Set PRDT addr: " + h(prdt_addr), LOG_DISK);
}
this.io.register_read(this.master_port | 2, dma_read_status);
this.io.register_write(this.master_port | 2, dma_write_status);
function dma_read_status()
{
dbg_log("DMA read status: " + h(dma_status), LOG_DISK);
return dma_status;
}
function dma_write_status(value)
{
dbg_log("DMA set status: " + h(value), LOG_DISK);
dma_status &= ~value;
}
this.io.register_read(this.master_port, dma_read_command);
this.io.register_write(this.master_port, dma_write_command);
function dma_read_command()
{
dbg_log("DMA read command", LOG_DISK);
return 1;
}
function dma_write_command(value)
{
dbg_log("DMA write command: " + h(value), LOG_DISK);
if(value & 1)
{
push_irq();
}
}
}