blob: c79405fad936053557676cfb9f6eccf349f719c2 [file] [log] [blame] [raw]
"use strict";
// http://wiki.osdev.org/PCI
var
/** @const */ PCI_CONFIG_ADDRESS = 0xCF8,
/** @const */ PCI_CONFIG_DATA = 0xCFC;
/**
* @constructor
* @param {CPU} cpu
*/
function PCI(cpu)
{
this.pci_addr = new Uint8Array(4);
this.pci_value = new Uint8Array(4);
this.pci_response = new Uint8Array(4);
this.pci_status = new Uint8Array(4);
this.pci_addr32 = new Int32Array(this.pci_addr.buffer);
this.pci_value32 = new Int32Array(this.pci_value.buffer);
this.pci_response32 = new Int32Array(this.pci_response.buffer);
this.pci_status32 = new Int32Array(this.pci_status.buffer);
this.device_spaces = [];
this.devices = [];
/** @const @type {CPU} */
this.cpu = cpu;
for(var i = 0; i < 256; i++)
{
this.device_spaces[i] = undefined;
this.devices[i] = undefined;
}
this.io = cpu.io;
cpu.io.register_write(PCI_CONFIG_DATA, this,
function(value)
{
this.pci_write8(this.pci_addr32[0], value);
},
function(value)
{
this.pci_write16(this.pci_addr32[0], value);
},
function(value)
{
this.pci_write32(this.pci_addr32[0], value);
});
cpu.io.register_write(PCI_CONFIG_DATA + 1, this,
function(value)
{
this.pci_write8(this.pci_addr32[0] + 1 | 0, value);
});
cpu.io.register_write(PCI_CONFIG_DATA + 2, this,
function(value)
{
this.pci_write8(this.pci_addr32[0] + 2 | 0, value);
},
function(value)
{
this.pci_write16(this.pci_addr32[0] + 2 | 0, value);
});
cpu.io.register_write(PCI_CONFIG_DATA + 3, this,
function(value)
{
this.pci_write8(this.pci_addr32[0] + 3 | 0, value);
});
cpu.io.register_read_consecutive(PCI_CONFIG_DATA, this,
function()
{
return this.pci_response[0];
},
function()
{
return this.pci_response[1];
},
function()
{
return this.pci_response[2];
},
function()
{
return this.pci_response[3];
}
);
cpu.io.register_read_consecutive(PCI_CONFIG_ADDRESS, this,
function()
{
return this.pci_status[0];
},
function()
{
return this.pci_status[1];
},
function()
{
return this.pci_status[2];
},
function()
{
return this.pci_status[3];
}
);
cpu.io.register_write_consecutive(PCI_CONFIG_ADDRESS, this,
function(out_byte)
{
this.pci_addr[0] = out_byte & 0xFC;
},
function(out_byte)
{
this.pci_addr[1] = out_byte;
},
function(out_byte)
{
this.pci_addr[2] = out_byte;
},
function(out_byte)
{
this.pci_addr[3] = out_byte;
this.pci_query();
}
);
// Some experimental PCI devices taken from my PC:
// 00:00.0 Host bridge: Intel Corporation 4 Series Chipset DRAM Controller (rev 02)
//var host_bridge = {
// pci_id: 0,
// pci_space: [
// 0x86, 0x80, 0x20, 0x2e, 0x06, 0x00, 0x90, 0x20, 0x02, 0x00, 0x00, 0x06, 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, 0x43, 0x10, 0xd3, 0x82,
// 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ],
// pci_bars: [],
//};
var host_bridge = {
pci_id: 0,
pci_space: [
// 00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
0x86, 0x80, 0x37, 0x12, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 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,
],
pci_bars: [],
name: "82441FX PMC",
};
this.register_device(host_bridge);
this.isa_bridge = {
pci_id: 1 << 3,
pci_space: [
// 00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
0x86, 0x80, 0x00, 0x70, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x06, 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, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
],
pci_bars: [],
name: "82371SB PIIX3 ISA",
};
this.isa_bridge_space = this.register_device(this.isa_bridge);
this.isa_bridge_space8 = new Uint8Array(this.isa_bridge_space.buffer);
// 00:1e.0 PCI bridge: Intel Corporation 82801 PCI Bridge (rev 90)
//this.register_device([
// 0x86, 0x80, 0x4e, 0x24, 0x07, 0x01, 0x10, 0x00, 0x90, 0x01, 0x04, 0x06, 0x00, 0x00, 0x01, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x20, 0xe0, 0xe0, 0x80, 0x22,
// 0xb0, 0xfe, 0xb0, 0xfe, 0xf1, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x02, 0x00,
//], 0x1e << 3);
}
PCI.prototype.get_state = function()
{
var state = [];
for(var i = 0; i < 256; i++)
{
state[i] = this.device_spaces[i];
}
state[256] = this.pci_addr;
state[257] = this.pci_value;
state[258] = this.pci_response;
state[259] = this.pci_status;
return state;
};
PCI.prototype.set_state = function(state)
{
for(var i = 0; i < 256; i++)
{
var device = this.devices[i];
var space = state[i];
if(!device || !space)
{
if(device)
{
dbg_log("Warning: While restoring PCI device: Device exists in current " +
"configuration but not in snapshot (" + device.name + ")");
}
if(space)
{
dbg_log("Warning: While restoring PCI device: Device doesn't exist in current " +
"configuration but does in snapshot (device " + h(i, 2) + ")");
}
continue;
}
for(var bar_nr = 0; bar_nr < device.pci_bars.length; bar_nr++)
{
var value = space[(0x10 >> 2) + bar_nr];
if(value & 1)
{
var bar = device.pci_bars[bar_nr];
var from = bar.original_bar & ~1 & 0xFFFF;
var to = value & ~1 & 0xFFFF;
this.set_io_bars(bar, from, to);
}
else
{
// memory, cannot be changed
}
}
this.device_spaces[i].set(space);
}
this.pci_addr.set(state[256]);
this.pci_value.set(state[257]);
this.pci_response.set(state[258]);
this.pci_status.set(state[259]);
};
PCI.prototype.pci_query = function()
{
var dbg_line = "query";
// Bit | .31 .0
// Fmt | EBBBBBBBBDDDDDFFFRRRRRR00
var bdf = this.pci_addr[2] << 8 | this.pci_addr[1],
addr = this.pci_addr[0] & 0xFC,
//devfn = bdf & 0xFF,
//bus = bdf >> 8,
dev = bdf >> 3 & 0x1F,
//fn = bdf & 7,
enabled = this.pci_addr[3] >> 7;
dbg_line += " enabled=" + enabled;
dbg_line += " bdf=" + h(bdf, 4);
dbg_line += " dev=" + h(dev, 2);
dbg_line += " addr=" + h(addr, 2);
var device = this.device_spaces[bdf];
if(device !== undefined)
{
this.pci_status32[0] = 0x80000000 | 0;
if(addr < device.byteLength)
{
this.pci_response32[0] = device[addr >> 2];
}
else
{
// required by freebsd-9.1
this.pci_response32[0] = 0;
}
dbg_line += " " + h(this.pci_addr32[0] >>> 0, 8) + " -> " + h(this.pci_response32[0] >>> 0, 8);
if(addr >= device.byteLength)
{
dbg_line += " (undef)";
}
dbg_line += " (" + this.devices[bdf].name + ")";
dbg_log(dbg_line, LOG_PCI);
}
else
{
this.pci_response32[0] = -1;
this.pci_status32[0] = 0;
}
};
PCI.prototype.pci_write8 = function(address, written)
{
var bdf = address >> 8 & 0xFFFF;
var addr = address & 0xFF;
var space = new Uint8Array(this.device_spaces[bdf].buffer);
var device = this.devices[bdf];
if(!space)
{
return;
}
dbg_assert(!(addr >= 0x10 && addr < 0x2C || addr >= 0x30 && addr < 0x34),
"PCI: Expected 32-bit write");
dbg_log("PCI write8 dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written, 2), LOG_PCI);
space[addr] = written;
};
PCI.prototype.pci_write16 = function(address, written)
{
dbg_assert((address & 1) === 0);
var bdf = address >> 8 & 0xFFFF;
var addr = address & 0xFF;
var space = new Uint16Array(this.device_spaces[bdf].buffer);
var device = this.devices[bdf];
if(!space)
{
return;
}
dbg_assert(!(addr >= 0x10 && addr < 0x2C || addr >= 0x30 && addr < 0x34),
"PCI: Expected 32-bit write");
dbg_log("PCI writ16 dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written, 4), LOG_PCI);
space[addr >>> 1] = written;
};
PCI.prototype.pci_write32 = function(address, written)
{
dbg_assert((address & 3) === 0);
var bdf = address >> 8 & 0xFFFF;
var addr = address & 0xFF;
var space = this.device_spaces[bdf];
var device = this.devices[bdf];
if(!space)
{
return;
}
if(addr >= 0x10 && addr < 0x28)
{
var bar_nr = addr - 0x10 >> 2;
var bar = device.pci_bars[bar_nr];
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed to " +
h(written >>> 0) + " dev=" + h(bdf >> 3, 2) + " (" + device.name + ") ", LOG_PCI);
if(bar)
{
dbg_assert(!(bar.size & bar.size - 1), "bar size should be power of 2");
var space_addr = addr >> 2;
var type = space[space_addr] & 1;
if((written | 3 | bar.size - 1) === -1) // size check
{
written = ~(bar.size - 1) | type;
if(type === 0)
{
space[space_addr] = written;
}
}
else
{
if(type === 0)
{
// memory
var original_bar = bar.original_bar;
if((written & ~0xF) !== (original_bar & ~0xF))
{
// seabios
dbg_log("Warning: Changing memory bar not supported, ignored", LOG_PCI);
}
// changing isn't supported yet, reset to default
space[space_addr] = original_bar;
}
}
if(type === 1)
{
// io
dbg_assert(type === 1);
var from = space[space_addr] & ~1 & 0xFFFF;
var to = written & ~1 & 0xFFFF;
dbg_log("io bar changed from " + h(from >>> 0, 8) +
" to " + h(to >>> 0, 8) + " size=" + bar.size, LOG_PCI);
this.set_io_bars(bar, from, to);
space[space_addr] = written | 1;
}
}
else
{
space[addr >> 2] = 0;
}
dbg_log("BAR effective value: " + h(space[addr >> 2] >>> 0), LOG_PCI);
}
else if(addr === 0x30)
{
dbg_log("PCI write rom address dev=" + h(bdf >> 3, 2) + " (" + device.name + ")" +
" value=" + h(written >>> 0, 8), LOG_PCI);
if(device.pci_rom_size)
{
if((written | 0x7FF) === (0xFFFFFFFF|0))
{
space[addr >> 2] = -device.pci_rom_size | 0;
}
else
{
space[addr >> 2] = device.pci_rom_address | 0;
}
}
else
{
space[addr >> 2] = 0;
}
}
else
{
dbg_log("PCI write dev=" + h(bdf >> 3, 2) + " (" + device.name + ") addr=" + h(addr, 4) +
" value=" + h(written >>> 0, 8), LOG_PCI);
space[addr >>> 2] = written;
}
};
PCI.prototype.register_device = function(device)
{
dbg_assert(device.pci_id !== undefined);
dbg_assert(device.pci_space !== undefined);
dbg_assert(device.pci_bars !== undefined);
var device_id = device.pci_id;
dbg_log("PCI register bdf=" + h(device_id) + " (" + device.name + ")", LOG_PCI);
dbg_assert(!this.devices[device_id]);
dbg_assert(device.pci_space.length >= 64);
dbg_assert(device_id < this.devices.length);
// convert bytewise notation from lspci to double words
var space = new Int32Array(64);
space.set(new Int32Array(new Uint8Array(device.pci_space).buffer));
this.device_spaces[device_id] = space;
this.devices[device_id] = device;
var bar_space = space.slice(4, 10);
for(var i = 0; i < device.pci_bars.length; i++)
{
var bar = device.pci_bars[i];
if(!bar)
{
continue;
}
var bar_base = bar_space[i];
var type = bar_base & 1;
bar.original_bar = bar_base;
bar.entries = [];
if(type === 0)
{
// memory, not needed currently
}
else
{
dbg_assert(type === 1);
var port = bar_base & ~1;
for(var j = 0; j < bar.size; j++)
{
bar.entries[j] = this.io.ports[port + j];
}
}
}
return space;
};
PCI.prototype.set_io_bars = function(bar, from, to)
{
var count = bar.size;
dbg_log("Move io bars: from=" + h(from) + " to=" + h(to) + " count=" + count, LOG_PCI);
var ports = this.io.ports;
for(var i = 0; i < count; i++)
{
var old_entry = ports[from + i];
ports[from + i] = this.io.create_empty_entry();
if(old_entry.read8 === this.io.empty_port_read8 &&
old_entry.read16 === this.io.empty_port_read16 &&
old_entry.read32 === this.io.empty_port_read32 &&
old_entry.write8 === this.io.empty_port_write &&
old_entry.write16 === this.io.empty_port_write &&
old_entry.write32 === this.io.empty_port_write)
{
dbg_log("Move IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI);
}
var entry = bar.entries[i];
var empty_entry = ports[to + i];
dbg_assert(entry && empty_entry);
ports[to + i] = entry;
// these can fail if the os maps an io port in multiple bars (indicating a bug)
dbg_assert(empty_entry.read8 === this.io.empty_port_read8, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.read16 === this.io.empty_port_read16, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.read32 === this.io.empty_port_read32, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write8 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write16 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
dbg_assert(empty_entry.write32 === this.io.empty_port_write, "Bad IO bar: Target already mapped");
}
};
PCI.prototype.raise_irq = function(pci_id)
{
var space = this.device_spaces[pci_id];
dbg_assert(space);
var pin = (space[0x3C >>> 2] >> 8 & 0xFF) - 1;
var device = (pci_id >> 3) - 1 & 0xFF;
var parent_pin = pin + device & 3;
var irq = this.isa_bridge_space8[0x60 + parent_pin];
//dbg_log("PCI raise irq " + h(irq) + " dev=" + h(device, 2) +
// " (" + this.devices[pci_id].name + ")", LOG_PCI);
this.cpu.device_raise_irq(irq);
};
PCI.prototype.lower_irq = function(pci_id)
{
var space = this.device_spaces[pci_id];
dbg_assert(space);
var pin = space[0x3C >>> 2] >> 8 & 0xFF;
var device = pci_id >> 3 & 0xFF;
var parent_pin = pin + device - 2 & 3;
var irq = this.isa_bridge_space8[0x60 + parent_pin];
//dbg_log("PCI lower irq " + h(irq) + " dev=" + h(device, 2) +
// " (" + this.devices[pci_id].name + ")", LOG_PCI);
this.cpu.device_lower_irq(irq);
};