merge changes from JoeOsborn's last changeset into current master

Merged all functionally relevant changes, omitted:

1. Left out almost all of the "dbg_log()" changes, they're like 90% of the original PR and make the essential changes in the diffs a bit hard to read, these can be added back in at a later point in time.
2. Also left out the "wants_cdrom" option and implemented its desired effect with "cdrom: { ejected: true }" as was suggested.
3. The test "tests/api/cdrom-insert-eject.js" is also left out, can be added later back in.

This patch should behave like the last changeset from JoeOsborn in 2023.
This commit is contained in:
Christian Schnell 2025-05-03 16:26:03 +02:00 committed by Fabian
parent 760fff6af8
commit 3c944a02e0
3 changed files with 217 additions and 118 deletions

View file

@ -119,6 +119,14 @@ import { EEXIST, ENOENT } from "../../lib/9p.js";
* }
* ```
*
* In order to create a CD-ROM device with ejected disk use:
*
* ```javascript
* cdrom: {
* ejected: true
* }
* ```
*
* @param {{
disable_mouse: (boolean|undefined),
disable_keyboard: (boolean|undefined),
@ -457,6 +465,13 @@ V86.prototype.continue_init = async function(emulator, options)
return;
}
if(name === "cdrom" && file.ejected)
{
// the "ejected file" is a special CD-ROM file object, pass it to settings.cdrom and let CPU.init() handle it
settings.cdrom = file;
return;
}
if(file.get && file.set && file.load)
{
files_to_load.push({
@ -984,6 +999,40 @@ V86.prototype.eject_fda = function()
this.v86.cpu.devices.fdc.eject_fda();
};
/**
* Set the image inserted in the CD-ROM drive. Can be changed at runtime, as
* when physically changing the CD-ROM.
*/
V86.prototype.set_cdrom = async function(file)
{
if(file.url && !file.async)
{
load_file(file.url, {
done: result =>
{
this.v86.cpu.devices.cdrom.master.set_cdrom(new SyncBuffer(result));
},
});
}
else
{
const image = buffer_from_object(file, this.zstd_decompress_worker.bind(this));
image.onload = () =>
{
this.v86.cpu.devices.cdrom.master.set_cdrom(image);
};
await image.load();
}
};
/**
* Eject the CD-ROM.
*/
V86.prototype.eject_cdrom = function()
{
this.v86.cpu.devices.cdrom.master.eject();
};
/**
* Send a sequence of scan codes to the emulated PS2 controller. A list of
* codes can be found at http://stanislavs.org/helppc/make_codes.html.

View file

@ -1,7 +1,7 @@
import { LOG_DISK } from "./const.js";
import { h } from "./lib.js";
import { dbg_assert, dbg_log } from "./log.js";
import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL } from "./rtc.js";
import { CMOS_BIOS_DISKTRANSFLAG, CMOS_DISK_DATA, CMOS_DISK_DRIVE1_CYL, CMOS_DISK_DRIVE2_CYL } from "./rtc.js";
// For Types Only
import { CPU } from "./cpu.js";
@ -19,6 +19,9 @@ const HD_SECTOR_SIZE = 512;
* */
export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
{
master_buffer = master_buffer && master_buffer.ejected ? undefined : master_buffer;
slave_buffer = slave_buffer && slave_buffer.ejected ? undefined : slave_buffer;
this.master = new IDEInterface(this, cpu, master_buffer, is_cd, nr, 0, bus);
this.slave = new IDEInterface(this, cpu, slave_buffer, false, nr, 1, bus);
@ -51,7 +54,7 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
this.ata_port_high = this.ata_port | 0x204;
/** @type {number} */
this.master_port = 0xB400;
const master_port = 0xB400 + nr * 0x100;
this.pci_space = [
0x86, 0x80, 0x10, 0x70, 0x05, 0x00, 0xA0, 0x02,
@ -60,7 +63,7 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
this.ata_port_high & 0xFF | 1, this.ata_port_high >> 8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // second device
0x00, 0x00, 0x00, 0x00, // second device
this.master_port & 0xFF | 1, this.master_port >> 8, 0x00, 0x00,
master_port & 0xFF | 1, master_port >> 8, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x43, 0x10, 0xD4, 0x82,
@ -174,26 +177,38 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
{
dbg_log("1F2/bytecount: " + h(data), LOG_DISK);
this.master.bytecount = (this.master.bytecount << 8 | data) & 0xFFFF;
this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF;
if(slave_buffer)
{
this.slave.bytecount = (this.slave.bytecount << 8 | data) & 0xFFFF;
}
});
cpu.io.register_write(this.ata_port | 3, this, function(data)
{
dbg_log("1F3/sector: " + h(data), LOG_DISK);
this.master.sector = (this.master.sector << 8 | data) & 0xFFFF;
this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF;
if(slave_buffer)
{
this.slave.sector = (this.slave.sector << 8 | data) & 0xFFFF;
}
});
cpu.io.register_write(this.ata_port | 4, this, function(data)
{
dbg_log("1F4/sector low: " + h(data), LOG_DISK);
this.master.cylinder_low = (this.master.cylinder_low << 8 | data) & 0xFFFF;
this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF;
if(slave_buffer)
{
this.slave.cylinder_low = (this.slave.cylinder_low << 8 | data) & 0xFFFF;
}
});
cpu.io.register_write(this.ata_port | 5, this, function(data)
{
dbg_log("1F5/sector high: " + h(data), LOG_DISK);
this.master.cylinder_high = (this.master.cylinder_high << 8 | data) & 0xFFFF;
this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF;
if(slave_buffer)
{
this.slave.cylinder_high = (this.slave.cylinder_high << 8 | data) & 0xFFFF;
}
});
cpu.io.register_write(this.ata_port | 6, this, function(data)
{
@ -231,24 +246,26 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
{
dbg_log("lower irq", LOG_DISK);
this.cpu.device_lower_irq(this.irq);
// clear error and DF bits
this.current_interface.status &= ~(1 | (1 << 5));
this.current_interface.ata_command(data);
});
cpu.io.register_read(this.master_port | 4, this, undefined, undefined, this.dma_read_addr);
cpu.io.register_write(this.master_port | 4, this, undefined, undefined, this.dma_set_addr);
cpu.io.register_read(master_port | 4, this, undefined, undefined, this.dma_read_addr);
cpu.io.register_write(master_port | 4, this, undefined, undefined, this.dma_set_addr);
cpu.io.register_read(this.master_port, this,
cpu.io.register_read(master_port, this,
this.dma_read_command8, undefined, this.dma_read_command);
cpu.io.register_write(this.master_port, this,
cpu.io.register_write(master_port, this,
this.dma_write_command8, undefined, this.dma_write_command);
cpu.io.register_read(this.master_port | 2, this, this.dma_read_status);
cpu.io.register_write(this.master_port | 2, this, this.dma_write_status);
cpu.io.register_read(master_port | 2, this, this.dma_read_status);
cpu.io.register_write(master_port | 2, this, this.dma_write_status);
cpu.io.register_read(this.master_port | 0x8, this, function() {
cpu.io.register_read(master_port | 0x8, this, function() {
dbg_log("DMA read 0x8", LOG_DISK); return 0;
});
cpu.io.register_read(this.master_port | 0xA, this, function() {
cpu.io.register_read(master_port | 0xA, this, function() {
dbg_log("DMA read 0xA", LOG_DISK); return 0;
});
@ -259,16 +276,9 @@ export function IDEDevice(cpu, master_buffer, slave_buffer, is_cd, nr, bus)
IDEDevice.prototype.read_status = function()
{
if(this.current_interface.buffer)
{
var ret = this.current_interface.status;
dbg_log("ATA read status: " + h(ret, 2), LOG_DISK);
return ret;
}
else
{
return 0;
}
const ret = this.current_interface.status;
dbg_log("dev "+this.name+" ATA read status: " + h(ret, 2), LOG_DISK);
return ret;
};
IDEDevice.prototype.write_control = function(data)
@ -276,7 +286,7 @@ IDEDevice.prototype.write_control = function(data)
dbg_log("set device control: " + h(data, 2) + " interrupts " +
((data & 2) ? "disabled" : "enabled"), LOG_DISK);
if(data & 4)
if(data & 0x04)
{
dbg_log("Reset via control port", LOG_DISK);
@ -337,7 +347,7 @@ IDEDevice.prototype.dma_write_command8 = function(value)
dbg_log("DMA write command8: " + h(value), LOG_DISK);
const old_command = this.dma_command;
this.dma_command = value & 0x9;
this.dma_command = value & 0x09;
if((old_command & 1) === (value & 1))
{
@ -371,7 +381,11 @@ IDEDevice.prototype.dma_write_command8 = function(value)
default:
dbg_log("Spurious dma command write, current command: " +
h(this.current_interface.current_command), LOG_DISK);
dbg_assert(false);
dbg_log("dev "+this.name+" DMA clear status 1 bit, set status 2 bit", LOG_DISK);
this.dma_status &= ~1;
this.dma_status |= 2;
this.push_irq();
break;
}
};
@ -394,7 +408,7 @@ IDEDevice.prototype.get_state = function()
state[3] = this.irq;
state[4] = this.pci_id;
state[5] = this.ata_port_high;
state[6] = this.master_port;
// state[6] = this.master_port;
state[7] = this.name;
state[8] = this.device_control;
state[9] = this.prdt_addr;
@ -412,7 +426,7 @@ IDEDevice.prototype.set_state = function(state)
this.irq = state[3];
this.pci_id = state[4];
this.ata_port_high = state[5];
this.master_port = state[6];
// this.master_port = state[6];
this.name = state[7];
this.device_control = state[8];
this.prdt_addr = state[9];
@ -441,7 +455,7 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus)
/** @const @type {CPU} */
this.cpu = cpu;
this.buffer = buffer;
this.buffer = null;
/** @type {number} */
this.sector_size = is_cd ? CDROM_SECTOR_SIZE : HD_SECTOR_SIZE;
@ -453,7 +467,7 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus)
this.sector_count = 0;
/** @type {number} */
this.head_count = 0;
this.head_count = this.is_atapi ? 1 : 0;
/** @type {number} */
this.sectors_per_track = 0;
@ -461,70 +475,6 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus)
/** @type {number} */
this.cylinder_count = 0;
if(this.buffer)
{
this.sector_count = this.buffer.byteLength / this.sector_size;
if(this.sector_count !== (this.sector_count | 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
{
// "default" values: 16/63
// common: 255, 63
this.head_count = 16;
this.sectors_per_track = 63;
}
this.cylinder_count = this.sector_count / this.head_count / this.sectors_per_track;
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.floor(this.cylinder_count);
//this.sector_count = this.cylinder_count * this.head_count *
// this.sectors_per_track * this.sector_size;
}
//if(this.cylinder_count > 16383)
//{
// this.cylinder_count = 16383;
//}
// disk translation: lba
var rtc = cpu.devices.rtc;
// master
rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG,
rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4);
rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0);
var reg = CMOS_DISK_DRIVE1_CYL;
rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF);
rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF);
rtc.cmos_write(reg + 2, this.head_count & 0xFF);
rtc.cmos_write(reg + 3, 0xFF);
rtc.cmos_write(reg + 4, 0xFF);
rtc.cmos_write(reg + 5, 0xC8);
rtc.cmos_write(reg + 6, this.cylinder_count & 0xFF);
rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF);
rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF);
//rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG,
// rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << (nr * 4 + 2)); // slave
}
this.buffer = buffer;
/** @type {number} */
this.is_lba = 0;
@ -585,9 +535,91 @@ function IDEInterface(device, cpu, buffer, is_cd, device_nr, interface_nr, bus)
this.in_progress_io_ids = new Set();
this.cancelled_io_ids = new Set();
if(buffer)
{
this.set_cdrom(buffer);
}
Object.seal(this);
}
IDEInterface.prototype.eject = function()
{
if(this.is_atapi && this.buffer)
{
this.buffer = null;
this.status = 0x59;
this.error = 0x60;
this.push_irq();
}
};
IDEInterface.prototype.set_cdrom = function(buffer)
{
if(!buffer)
{
this.eject();
return;
}
this.buffer = buffer;
if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true
{
this.status = 0x59;
this.error = 0x60;
}
this.sector_count = this.buffer.byteLength / this.sector_size;
if(this.sector_count !== (this.sector_count | 0))
{
dbg_log("Warning: Disk size not aligned with sector size", LOG_DISK);
this.sector_count = Math.ceil(this.sector_count);
}
if(this.is_atapi) /// TODO: redundant here in set_cdrom()? is_atapi === is_cd === true
{
// default values: 1/2048
this.head_count = 1;
this.sectors_per_track = 2048;
}
else /// TODO: dead code?
{
// "default" values: 16/63
// common: 255, 63
this.head_count = 16;
this.sectors_per_track = 63;
}
this.cylinder_count = this.sector_count / this.head_count / this.sectors_per_track;
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.floor(this.cylinder_count);
}
var rtc = this.cpu.devices.rtc;
// master
rtc.cmos_write(CMOS_BIOS_DISKTRANSFLAG,
rtc.cmos_read(CMOS_BIOS_DISKTRANSFLAG) | 1 << this.nr * 4);
rtc.cmos_write(CMOS_DISK_DATA, rtc.cmos_read(CMOS_DISK_DATA) & 0x0F | 0xF0);
var reg = this.nr == 0 ? CMOS_DISK_DRIVE1_CYL : CMOS_DISK_DRIVE2_CYL;
rtc.cmos_write(reg + 0, this.cylinder_count & 0xFF);
rtc.cmos_write(reg + 1, this.cylinder_count >> 8 & 0xFF);
rtc.cmos_write(reg + 2, this.head_count & 0xFF);
rtc.cmos_write(reg + 3, 0xFF);
rtc.cmos_write(reg + 4, 0xFF);
rtc.cmos_write(reg + 5, 0xC8);
rtc.cmos_write(reg + 6, this.cylinder_count & 0xFF);
rtc.cmos_write(reg + 7, this.cylinder_count >> 8 & 0xFF);
rtc.cmos_write(reg + 8, this.sectors_per_track & 0xFF);
if(this.device.cpu) {
this.push_irq();
}
};
IDEInterface.prototype.device_reset = function()
{
if(this.is_atapi)
@ -623,7 +655,7 @@ IDEInterface.prototype.ata_command = function(cmd)
{
dbg_log("ATA Command: " + h(cmd) + " slave=" + (this.drive_head >> 4 & 1), LOG_DISK);
if(!this.buffer)
if((!this.buffer && cmd != 0xA1 && cmd != 0xEC && cmd != 0xA0))
{
dbg_log("abort: No buffer", LOG_DISK);
this.error = 4;
@ -855,6 +887,21 @@ IDEInterface.prototype.atapi_handle = function()
this.data_pointer = 0;
this.current_atapi_command = this.data[0];
if(!this.buffer && (this.current_atapi_command == 0x25 ||
this.current_atapi_command == 0x28 ||
this.current_atapi_command == 0x42 ||
this.current_atapi_command == 0x43 ||
this.current_atapi_command == 0x51))
{
dbg_log("dev "+this.device.name+" CD read-related action: no buffer", LOG_DISK);
this.status = 0x51;
this.error = 0x21;
this.data_allocate(0);
this.data_end = this.data_length;
this.bytecount = this.bytecount & ~7 | 2 | 1;
this.push_irq();
return;
}
switch(this.current_atapi_command)
{
@ -873,7 +920,7 @@ IDEInterface.prototype.atapi_handle = function()
this.status = 0x58;
this.data[0] = 0x80 | 0x70;
this.data[2] = 5; // illegal request
this.data[2] = this.error >> 4;
this.data[7] = 8;
break;
@ -1146,9 +1193,16 @@ IDEInterface.prototype.atapi_read = function(cmd)
req_length = byte_count;
}
if(start >= this.buffer.byteLength)
if(!this.buffer)
{
dbg_assert(false, "CD read: Outside of disk end=" + h(start + byte_count) +
dbg_assert(false, "dev "+this.device.name+" CD read: no buffer", LOG_DISK);
this.status = 0xFF;
this.error = 0x41;
this.push_irq();
}
else if(start >= this.buffer.byteLength)
{
dbg_assert(false, "dev "+this.device.name+" CD read: Outside of disk end=" + h(start + byte_count) +
" size=" + h(this.buffer.byteLength), LOG_DISK);
this.status = 0xFF;
@ -1643,10 +1697,9 @@ IDEInterface.prototype.do_ata_read_sectors_dma = function()
this.device.dma_status &= ~1;
this.current_command = -1;
this.push_irq();
this.report_read_end(byte_count);
//}.bind(this), 10);
this.push_irq();
});
};
@ -1833,13 +1886,6 @@ IDEInterface.prototype.create_identify_packet = function()
{
// http://bochs.sourceforge.net/cgi-bin/lxr/source/iodev/harddrv.cc#L2821
if(this.drive_head & 0x10)
{
// slave
this.data_allocate(0);
return;
}
for(var i = 0; i < 512; i++)
{
this.data[i] = 0;

View file

@ -394,7 +394,7 @@ PCI.prototype.pci_write32 = function(address, written)
var bar_nr = addr - 0x10 >> 2;
var bar = device.pci_bars[bar_nr];
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed to " +
dbg_log("BAR" + bar_nr + " exists=" + (bar ? "y" : "n") + " changed from " + h(space[addr >> 2]) + " to " +
h(written >>> 0) + " dev=" + h(bdf >> 3, 2) + " (" + device.name + ") ", LOG_PCI);
if(bar)
@ -518,6 +518,7 @@ PCI.prototype.register_device = function(device)
var bar_base = bar_space[i];
var type = bar_base & 1;
dbg_log("device "+ device.name +" register bar of size "+bar.size +" at " + h(bar_base), LOG_PCI);
bar.original_bar = bar_base;
bar.entries = [];
@ -565,6 +566,9 @@ PCI.prototype.set_io_bars = function(bar, from, to)
old_entry.write32 === this.io.empty_port_write)
{
// happens when a device doesn't register its full range (currently ne2k and virtio)
// but it also happens when aligned reads/writes are set up,
// e.g. a 16-bit read registered at 0xB400 will show up as
// no source mapping at 0xB401.
dbg_log("Warning: Bad IO bar: Source not mapped, port=" + h(from + i, 4), LOG_PCI);
}
@ -577,16 +581,16 @@ PCI.prototype.set_io_bars = function(bar, from, to)
ports[to + i] = entry;
}
if(empty_entry.read8 === this.io.empty_port_read8 ||
empty_entry.read16 === this.io.empty_port_read16 ||
empty_entry.read32 === this.io.empty_port_read32 ||
empty_entry.write8 === this.io.empty_port_write ||
empty_entry.write16 === this.io.empty_port_write ||
empty_entry.write32 === this.io.empty_port_write)
if(empty_entry.read8 !== this.io.empty_port_read8 ||
empty_entry.read16 !== this.io.empty_port_read16 ||
empty_entry.read32 !== this.io.empty_port_read32 ||
empty_entry.write8 !== this.io.empty_port_write ||
empty_entry.write16 !== this.io.empty_port_write ||
empty_entry.write32 !== this.io.empty_port_write)
{
// These can fail if the os maps an io port in multiple bars (indicating a bug)
// XXX: Fails during restore_state
dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4), LOG_PCI);
dbg_log("Warning: Bad IO bar: Target already mapped, port=" + h(to + i, 4)+" from device "+entry.device.name+" to device "+empty_entry.device.name, LOG_PCI);
}
}
};