| "use strict"; |
| |
| |
| var |
| /** @const */ |
| CDROM_SECTOR_SIZE = 2048, |
| /** @const */ |
| HD_SECTOR_SIZE = 512; |
| |
| |
| /** @constructor */ |
| function CDRom(dev, cd_buffer) |
| { |
| this.io = dev.io; |
| this.memory = dev.memory; |
| this.pic = dev.pic; |
| this.pci = dev.pci; |
| |
| this.vendor_id = 0x1002; |
| this.class_revision = 0x106 << 16 | 0x01 << 8; |
| this.irq = 14; |
| this.iobase = 0xFFF10000; |
| this.sector_size = CDROM_SECTOR_SIZE; |
| this.buffer = cd_buffer; |
| this.atapi = true; |
| this.pci_id = 8; |
| |
| this.init(); |
| } |
| CDRom.prototype = new AHCIDevice(); |
| |
| /** @constructor */ |
| function HDD(dev, disk_buffer, nr) |
| { |
| var port = nr === 0 ? 0x1F0 : 0x170, |
| irq = nr === 0 ? 14 : 15; |
| |
| var pic = dev.pic; |
| |
| this.io = dev.io; |
| this.memory = dev.memory; |
| this.pic = dev.pic; |
| this.pci = dev.pci; |
| |
| this.vendor_id = 0x1002; |
| this.class_revision = 0x106 << 16 | 0x01 << 8; |
| this.irq = irq; |
| this.iobase = 0xFFF00000; |
| this.sector_size = HD_SECTOR_SIZE; |
| this.sector_count = disk_buffer.byteLength / this.sector_size; |
| this.buffer = disk_buffer; |
| this.atapi = false; |
| this.pci_id = 0x10; |
| |
| this.head_count = 16; |
| this.sectors_per_track = 63; |
| |
| this.cylinder_count = disk_buffer.byteLength / |
| this.head_count / (this.sectors_per_track + 1) / this.sector_size; |
| |
| dbg_assert(this.cylinder_count === (this.cylinder_count | 0)); |
| dbg_assert(this.cylinder_count <= 16383); |
| |
| var me = this; |
| |
| // status |
| this.io.register_read(port | 7, read_status); |
| |
| // alternate status, starting at 3f6/376 |
| this.io.register_read(port | 0x206, read_status); |
| |
| function read_status() |
| { |
| dbg_log("ATA read status", LOG_DISK); |
| |
| var status = 0x50; |
| |
| if(data_pointer < pio_data.length) |
| status |= 8; |
| |
| return status; |
| } |
| |
| var last_drive = 0xFF, |
| data_pointer = 0, |
| pio_data = [], |
| drq = false, |
| is_lba = 0, |
| slave = 0, |
| bytecount = 0, |
| sector = 0, |
| cylinder = 0, |
| head = 0; |
| |
| |
| function push_irq() |
| { |
| pic.push_irq(me.irq); |
| } |
| |
| this.io.register_write(port | 6, function(data) |
| { |
| dbg_log("1F6 write " + h(data), LOG_DISK); |
| |
| var slave = data & 0x10, |
| mode = data & 0xE0, |
| low = data & 0xF; |
| |
| |
| if(slave) |
| { |
| //drq = false; |
| return; |
| } |
| |
| is_lba = data >> 6 & 1; |
| head = data & 0xF; |
| last_drive = data; |
| }); |
| |
| this.io.register_write(port | 2, function(data) |
| { |
| dbg_log("1F2 write: " + data, LOG_DISK); |
| if(data) |
| { |
| bytecount = data << 9; |
| } |
| else |
| { |
| bytecount = 256 << 9; |
| } |
| //bytecount = 1 << 9; |
| }); |
| this.io.register_write(port | 3, function(data) |
| { |
| sector = data; |
| }); |
| this.io.register_write(port | 4, function(data) |
| { |
| cylinder = cylinder & 0xFF00 | data; |
| }); |
| this.io.register_write(port | 5, function(data) |
| { |
| cylinder = cylinder & 0xFF | data << 8; |
| }); |
| |
| this.io.register_write(port | 7, function(cmd) |
| { |
| if(cmd === 0xEC) |
| { |
| dbg_log("ATA identify device", LOG_DISK); |
| // identify device |
| // http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821 |
| |
| data_pointer = 0; |
| |
| pio_data = new Uint8Array([ |
| 0x40, 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 |
| 0, 0, |
| 1, 0, |
| 0, 3, // capabilities |
| // 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 |
| this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, |
| this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, |
| |
| 0, 0, |
| // 60 |
| this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, |
| this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, |
| |
| 0, 0, 0, 0, 0, 0, |
| // 65 |
| 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, |
| // 85 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| // 90 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| // 95 |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| // 100 |
| this.sector_count & 0xFF, this.sector_count >> 8 & 0xFF, |
| this.sector_count >> 16 & 0xFF, this.sector_count >> 24 & 0xFF, |
| |
| |
| ]); |
| |
| push_irq(); |
| } |
| else if(cmd === 0x91) |
| { |
| dbg_log("ATA cmd 91", LOG_DISK); |
| push_irq(); |
| } |
| else if(cmd === 0x10) |
| { |
| // obsolete |
| dbg_log("ATA cmd 10", LOG_DISK); |
| push_irq(); |
| } |
| else if(cmd = 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 = [ |
| 0, 0, // error |
| 0, 0, // count |
| |
| // result |
| disk_buffer.byteLength & 0xff, |
| disk_buffer.byteLength >> 8 & 0xff, |
| disk_buffer.byteLength >> 16 & 0xff, |
| disk_buffer.byteLength >> 24 & 0xff, |
| 0, 0, |
| |
| 0, 0, // |
| ]; |
| } |
| else if(cmd === 0x20) |
| { |
| if(DEBUG && is_lba) |
| throw "unimplemented"; |
| |
| var lba = (cylinder * me.head_count + head) * me.sectors_per_track + sector - 1; |
| dbg_log("ATA read: from=" + h(lba * me.sector_size) + " chs=" + cylinder + "/" + head + "/" + sector + " length=" + h(bytecount), LOG_DISK); |
| |
| me.buffer.get(lba * me.sector_size, bytecount, function(data) |
| { |
| data_pointer = 0; |
| pio_data = data; |
| |
| push_irq(); |
| }); |
| } |
| else |
| { |
| dbg_log("New ATA cmd on 1F7: " + h(cmd), LOG_DISK); |
| } |
| }); |
| |
| this.io.register_read(port | 0, function() |
| { |
| if(data_pointer < pio_data.length) |
| { |
| dbg_log("Read 1F0: " + h(pio_data[data_pointer], 2), LOG_DISK); |
| |
| if((data_pointer & 511) === 0) |
| push_irq(); |
| |
| return pio_data[data_pointer++] & 0xFF; |
| } |
| else |
| { |
| dbg_log("Read 1F0: empty", LOG_DISK); |
| return 0; |
| } |
| }); |
| |
| |
| this.io.register_read(port | 1, function() |
| { |
| dbg_log("Read 1F1", LOG_DISK); |
| return 0xFF; |
| }); |
| |
| this.io.register_read(port | 2, function() |
| { |
| dbg_log("Read 1F2", LOG_DISK); |
| return 0xFF; |
| }); |
| |
| |
| this.io.register_read(port | 3, function() |
| { |
| dbg_log("Read 1F3", LOG_DISK); |
| return 0xFF; |
| }); |
| |
| this.io.register_read(port | 6, function() |
| { |
| dbg_log("Read 1F6", LOG_DISK); |
| return last_drive; |
| }); |
| |
| this.init(); |
| } |
| HDD.prototype = new AHCIDevice(); |
| |
| |
| /** @constructor */ |
| function AHCIDevice() |
| { |
| var me, |
| memory; |
| |
| this.init = function() |
| { |
| me = this; |
| memory = this.memory; |
| |
| this.pci.register_device(this, this.pci_id); |
| |
| this.memory.mmap_register(this.iobase, 0x4000, true, mmio_read, mmio_write); |
| |
| }; |
| |
| var host_ctl = 0, |
| host_caps = 1, |
| host_ports_impl = 1, |
| host_intbits = 1, |
| port_lst_addr, |
| port_fis_addr; |
| |
| function atapi_command_read(atapi, dest, byte_len) |
| { |
| var lba = Math.to_be32(memory.read32s(atapi + 2)), |
| count = Math.to_be16(memory.read16(atapi + 7)), |
| flags = memory.read8(atapi + 1), |
| |
| //bytecount = Math.min(count * me.sector_size, byte_len + 1); |
| bytecount = count * me.sector_size; |
| |
| dbg_log("CD read lba=" + h(lba) + |
| " lbacount=" + h(count) + |
| " bytelen=" + h(byte_len) + |
| " copycount=" + h(bytecount) + |
| " flags=" + h(flags) + |
| " buf=" + h(dest, 8), LOG_CD); |
| |
| me.buffer.get(lba * me.sector_size, bytecount, function(data) |
| { |
| memory.write_blob(data, dest); |
| }); |
| |
| //pic.push_irq(me.irq); |
| } |
| |
| function ata_command_rw(cmd_fis, dest, is_read) |
| { |
| var lba = memory.read32s(cmd_fis + 4) & 0xFFFFFF, |
| count = memory.read16(cmd_fis + 12), |
| bytecount = count * me.sector_size; |
| |
| dbg_log("ahci " + (is_read ? "read" : "write") + ": lba=" + h(lba, 8) + |
| " count=" + h(count, 4) + |
| " dest=" + h(dest, 8)); |
| |
| if(is_read) |
| { |
| this.buffer.get(lba * me.sector_size, bytecount, function(data) |
| { |
| memory.write_blob(data, dest); |
| }); |
| } |
| else |
| { |
| this.buffer.set(lba * me.sector_size, |
| new Uint8Array(memory.buffer, dest, bytecount), |
| function() |
| { |
| }); |
| } |
| } |
| |
| |
| function mmio_read(addr) |
| { |
| switch(addr) |
| { |
| case 0: |
| return host_caps; |
| |
| case 4: |
| return host_ctl; |
| |
| case 0xC: |
| return host_ports_impl; |
| |
| case 0x128: |
| return 0x03; |
| |
| case 0x110: |
| return host_intbits; |
| |
| default: |
| dbg_log("New PCI mmio read from " + h(addr, 8), LOG_CD); |
| } |
| } |
| |
| function mmio_write(addr, value) |
| { |
| switch(addr) |
| { |
| case 0x100: |
| port_lst_addr = value; |
| dbg_log("lst at " + h(value, 8), LOG_CD); |
| break; |
| case 0x108: |
| port_fis_addr = value; |
| dbg_log("fis at " + h(value, 8), LOG_CD); |
| break; |
| |
| case 0x118: |
| dbg_log("port cmd: " + h(value, 8), LOG_CD); |
| break; |
| |
| case 0x138: |
| var |
| |
| ctba_addr = memory.read32s(port_lst_addr + 8), |
| |
| first_prdt_start = ctba_addr + 0x80, |
| flags = memory.read16(port_lst_addr), |
| prdt_addr = memory.read32s(first_prdt_start) + 0x100000000 * memory.read32s(first_prdt_start + 4), |
| prdt_len = memory.read32s(ctba_addr + 0x80 + 0xC) & 0xFFF, |
| atapi_command = memory.read8(ctba_addr + 0x40), |
| fis_command = memory.read8(ctba_addr + 2), |
| |
| dma_fis_start = port_fis_addr + 0, |
| pio_fis_start = port_fis_addr + 0x20, |
| d2h_fis_start = port_fis_addr + 0x40, |
| ufis_start = port_fis_addr + 0x60, |
| |
| command_fis_start = ctba_addr + 0, |
| atapi_command_start = ctba_addr + 0x40; |
| |
| if((fis_command === 0xA0 || fis_command === 0xA1) && |
| !me.atapi) |
| { |
| return; |
| } |
| |
| // status success |
| memory.write8(d2h_fis_start + 2, 0x40); |
| |
| dbg_log("ctba at " + h(ctba_addr), LOG_CD); |
| dbg_log("prdt at " + h(prdt_addr), LOG_CD); |
| dbg_log("flags: " + h(flags, 2), LOG_CD); |
| dbg_log("cmd fis command: " + h(fis_command, 2), LOG_CD); |
| |
| dbg_log("fis LBA=" + h(memory.read32s(command_fis_start + 4) & 0xffffff), LOG_CD); |
| |
| dbg_log("Prdts count: " + h(memory.read16(port_lst_addr + 2)), LOG_CD); |
| dbg_log("PRD byte count: " + h(memory.read32s(port_lst_addr + 4)), LOG_CD); |
| |
| dbg_log("First prdt byte count: " + h(memory.read32s(ctba_addr + 0x80 + 0xC)), LOG_CD); |
| |
| if(fis_command === 0xC8 || fis_command === 0xCA) |
| { |
| ata_command_rw(command_fis_start, prdt_addr, fis_command === 0xC8); |
| } |
| else if(fis_command === 0xEC) |
| { |
| // ATA_CMD_IDENTIFY_DEVICE |
| |
| // number of sectors |
| memory.write32(prdt_addr + 120, me.buffer.byteLength / me.sector_size); |
| } |
| else if(fis_command === 0xA1) |
| { |
| // ATA_CMD_IDENTIFY_PACKET_DEVICE |
| |
| // is CD |
| memory.write32(prdt_addr, 0x0500); |
| } |
| else if(fis_command === 0xA0) |
| { |
| // ATA_CMD_PACKET |
| if(atapi_command === 0x28) |
| { |
| atapi_command_read(ctba_addr + 0x40, prdt_addr, prdt_len); |
| } |
| else if(atapi_command === 0x2a) |
| { |
| // write |
| dbg_log("atapi - unimplemented write", LOG_CD); |
| } |
| else if(atapi_command === 0x25) |
| { |
| // read capacity |
| dbg_log("atapi - unimplemented read cap", LOG_CD); |
| } |
| else |
| { |
| dbg_log("atapi - unimplemented " + h(atapi_command, 2), LOG_CD); |
| } |
| } |
| else |
| { |
| dbg_log("unimplemented fis command: " + h(fis_command, 2)); |
| } |
| |
| break; |
| |
| default: |
| dbg_log("PCI mmio write addr=" + h(addr, 8) + " value=" + h(value, 8), LOG_CD); |
| } |
| } |
| } |
| |