Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Spice86.Core/CLI/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ public sealed class Configuration : CommandSettings {
[CommandOption("--Ems <EMS>")]
public bool? Ems { get; init; }

/// <summary>
/// Determines whether DOS Upper Memory Blocks (UMBs) are available.
/// </summary>
[CommandOption("--Umb <UMB>")]
[DefaultValue(true)]
public bool Umb { get; set; } = true;

/// <summary>
/// Specify the type of mouse to use.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Spice86.Core/CLI/RuntimeOptions/DosOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ namespace Spice86.Core.CLI.RuntimeOptions;
/// <param name="ProgramEntryPointSegment">Program load segment in emulated memory.</param>
/// <param name="Xms">Whether XMS services are enabled.</param>
/// <param name="Ems">Whether EMS services are enabled.</param>
/// <param name="Umb">Whether DOS UMB support is enabled.</param>
/// <param name="DosRuntimeState">Shared mutable DOS runtime state.</param>
public sealed record class DosOptions(
string? CDrive,
string Exe,
ushort ProgramEntryPointSegment,
bool? Xms,
bool? Ems,
bool Umb,
DosRuntimeState DosRuntimeState);
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public static DosOptions ToDosOptions(Configuration configuration, DosRuntimeSta
configuration.ProgramEntryPointSegment,
configuration.Xms,
configuration.Ems,
configuration.Umb,
dosRuntimeState);
}

Expand Down
28 changes: 17 additions & 11 deletions src/Spice86.Core/Emulator/InterruptHandlers/Dos/DosInt21Handler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -755,10 +755,9 @@ public void AllocateMemoryBlock(bool calledFromVm) {
LogDosError(calledFromVm);
// did not find something good, error
SetCarryFlag(true, calledFromVm);
DosMemoryControlBlock largest = _dosMemoryManager.FindLargestFree();
// INSUFFICIENT MEMORY
State.AX = (byte)DosErrorCode.InsufficientMemory;
State.BX = largest.Size;
State.BX = _dosMemoryManager.FindLargestFreeSizeForAllocation();
return;
}
State.AX = res.DataBlockSegment;
Expand Down Expand Up @@ -1751,20 +1750,27 @@ public enum AllocationStrategySubFunction : byte {
public void AllocationStrategyOrUpperMemoryLinkState(bool calledFromVm) {
byte op = State.AL;
if (op == (byte)AllocationStrategySubFunction.QueryMemoryAllocationStrategy) {
State.AX = (ushort)_dosMemoryManager.AllocationStrategy;
State.AX = _dosMemoryManager.AllocationStrategyValue;
SetCarryFlag(false, calledFromVm);
} else if (op == (byte)AllocationStrategySubFunction.SetMemoryAllocationStrategy) {
_dosMemoryManager.AllocationStrategy = (DosMemoryAllocationStrategy)State.BX;
State.AX = 0;
SetCarryFlag(false, calledFromVm);
if (_dosMemoryManager.TrySetAllocationStrategy(State.BX)) {
State.AX = 0;
SetCarryFlag(false, calledFromVm);
} else {
State.AX = 1;
SetCarryFlag(true, calledFromVm);
}
} else if (op == (byte)AllocationStrategySubFunction.QueryUpperMemoryBlockState) {
// 01H = upper memory is currently linked
// 00H = not linked (all allocations go to conventional mem)
State.AL = 0x00;
// AL bit 0 is set when the UMB chain is linked to the conventional MCB chain.
State.AL = _dosMemoryManager.UmbChainState;
SetCarryFlag(false, calledFromVm);
} else if (op == (byte)AllocationStrategySubFunction.SetUpperMemoryBlockState) {
State.AX = 0x01; // 0001h (invalid function)
SetCarryFlag(true, calledFromVm);
if (_dosMemoryManager.SetUmbChainLinkState(State.BX)) {
SetCarryFlag(false, calledFromVm);
} else {
State.AX = 0x01;
SetCarryFlag(true, calledFromVm);
}
} else {
throw GenerateUnhandledOperationException(op);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Spice86.Core.Emulator.InterruptHandlers.Dos.Xms;
using Spice86.Core.Emulator.CPU;
using Spice86.Core.Emulator.InterruptHandlers.Common.MemoryWriter;
using Spice86.Core.Emulator.Memory;
using Spice86.Core.Emulator.OperatingSystem;
using Spice86.Core.Emulator.OperatingSystem.Devices;
using Spice86.Core.Emulator.OperatingSystem.Structures;
using Spice86.Shared.Emulator.Memory;
Expand Down Expand Up @@ -110,6 +111,7 @@ public sealed class ExtendedMemoryManager : IVirtualDevice {
private readonly State _state;
private readonly A20Gate _a20Gate;
private readonly IMemory _memory;
private readonly DosMemoryManager _dosMemoryManager;
/// <summary>
/// Set on DOS XMS driver startup - we don't touch it if was enabled at startup.
/// </summary>
Expand Down Expand Up @@ -221,6 +223,7 @@ private class A20State {
/// <param name="state">The CPU state for accessing registers during XMS operations.</param>
/// <param name="memoryAsmWriter">Helper for writing assembly code to memory.</param>
/// <param name="dosTables">DOS memory tables for placing the XMS driver in memory.</param>
/// <param name="dosMemoryManager">The DOS memory manager used for UMB operations.</param>
/// <param name="loggerService">The logger service for recording XMS operations.</param>
/// <remarks>
/// This constructor:
Expand All @@ -232,7 +235,7 @@ private class A20State {
/// </remarks>
public ExtendedMemoryManager(IMemory memory, State state, A20Gate a20Gate,
MemoryAsmWriter memoryAsmWriter, DosTables dosTables,
ILogger loggerService) {
DosMemoryManager dosMemoryManager, ILogger loggerService) {
uint headerAddress = MemoryUtils.ToPhysicalAddress(DosDeviceSegment, 0);
Header = new DosDeviceHeader(memory,
headerAddress) {
Expand All @@ -243,6 +246,7 @@ public ExtendedMemoryManager(IMemory memory, State state, A20Gate a20Gate,
_state = state;
_a20Gate = a20Gate;
_memory = memory;
_dosMemoryManager = dosMemoryManager;
_loggerService = loggerService;
// Place hookable callback in writable memory area
var hookableCodeAddress = new SegmentedAddress((ushort)(dosTables
Expand Down Expand Up @@ -1522,7 +1526,37 @@ private void ReallocateExtendedMemoryBlockInternal(int handle, uint newSizeInByt
/// </remarks>
public void RequestUpperMemoryBlock() {
_state.AX = 0;
_state.BL = (byte)XmsErrorCodes.UmbNoBlocksAvailable;
_state.BL = (byte)XmsErrorCodes.NotImplemented;
DosMemoryManager dosMemoryManager = _dosMemoryManager;
if (!dosMemoryManager.HasUpperMemoryBlocks) {
return;
}

ushort previousStrategy = dosMemoryManager.AllocationStrategyValue;
byte previousLinkState = dosMemoryManager.UmbChainState;
if (previousLinkState == 0) {
dosMemoryManager.SetUmbChainLinkState(1);
}
dosMemoryManager.TrySetAllocationStrategy(0x40);
try {
DosMemoryControlBlock? block = dosMemoryManager.AllocateMemoryBlock(_state.DX);
if (block is not null) {
_state.AX = 1;
_state.BX = block.DataBlockSegment;
return;
}

ushort largestUmbBlock = dosMemoryManager.FindLargestFreeSizeForAllocation();
_state.DX = largestUmbBlock;
_state.BL = largestUmbBlock == 0
? (byte)XmsErrorCodes.UmbNoBlocksAvailable
: (byte)XmsErrorCodes.UmbOnlySmallerBlock;
} finally {
if (dosMemoryManager.UmbChainState != previousLinkState) {
dosMemoryManager.SetUmbChainLinkState(previousLinkState);
}
dosMemoryManager.TrySetAllocationStrategy(previousStrategy);
}
}

/// <summary>
Expand All @@ -1540,7 +1574,16 @@ public void RequestUpperMemoryBlock() {
/// </remarks>
public void ReleaseUpperMemoryBlock() {
_state.AX = 0;
_state.BL = (byte)XmsErrorCodes.NotImplemented;
_state.BL = (byte)XmsErrorCodes.UmbInvalidSegment;
DosMemoryManager dosMemoryManager = _dosMemoryManager;
if (!dosMemoryManager.HasUpperMemoryBlocks) {
return;
}

if (dosMemoryManager.FreeUpperMemoryBlock(_state.DX)) {
_state.AX = 1;
_state.BL = (byte)XmsErrorCodes.Ok;
}
}

/// <summary>
Expand Down
33 changes: 23 additions & 10 deletions src/Spice86.Core/Emulator/OperatingSystem/Dos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace Spice86.Core.Emulator.OperatingSystem;
using Spice86.Core.Emulator.Devices.Video;
using Spice86.Core.Emulator.Function;
using Spice86.Core.Emulator.InterruptHandlers.Bios.Structures;
using Spice86.Core.Emulator.InterruptHandlers.Common.MemoryWriter;
using Spice86.Core.Emulator.InterruptHandlers.Dos;
using Spice86.Core.Emulator.InterruptHandlers.Dos.Ems;
using Spice86.Core.Emulator.InterruptHandlers.Dos.Xms;
Expand Down Expand Up @@ -160,6 +161,9 @@ public sealed class Dos : IDriveStatusProvider, IDiscSwapper, IDriveMountService
/// </summary>
public ExpandedMemoryManager? Ems { get; private set; }

/// <summary>
/// The XMS device driver.
/// </summary>
public ExtendedMemoryManager? Xms { get; private set; }

/// <summary>
Expand Down Expand Up @@ -192,7 +196,9 @@ public sealed class Dos : IDriveStatusProvider, IDiscSwapper, IDriveMountService
/// <param name="loggerService">The logger service implementation.</param>
/// <param name="channelCreator">The sound channel creator, used to stream CD audio when an image is mounted.</param>
/// <param name="activityNotifier">Notifier that surfaces per-drive read/write activity to the UI.</param>
/// <param name="xms">Optional XMS manager to expose through DOS.</param>
/// <param name="a20Gate">The A20 gate used by XMS.</param>
/// <param name="memoryAsmWriter">Writes the XMS callback into emulated memory.</param>
/// <param name="emsManager">The EMS manager shared with emulator services.</param>
public Dos(DosOptions options, IMemory memory,
IFunctionHandlerProvider functionHandlerProvider, Stack stack, State state,
BiosKeyboardBuffer biosKeyboardBuffer, KeyboardInt16Handler keyboardInt16Handler,
Expand All @@ -201,14 +207,16 @@ public Dos(DosOptions options, IMemory memory,
FloppyDiskTimingService floppyDiskTimingService,
ISoundChannelCreator channelCreator,
IDriveActivityNotifier activityNotifier,
ExtendedMemoryManager? xms) {
A20Gate a20Gate,
MemoryAsmWriter memoryAsmWriter,
ExpandedMemoryManager? emsManager) {
_loggerService = loggerService;
Xms = xms;
_biosKeyboardBuffer = biosKeyboardBuffer;
_memory = memory;
_biosDataArea = biosDataArea;
_vgaFunctionality = vgaFunctionality;
_activityNotifier = activityNotifier;
Ems = emsManager;

DosTables = new DosTables(memory);
ushort mediaIdSegment = DosTables.ReserveDosPrivateSegment((ushort)DosMediaIdTable.TableSizeInParagraphs);
Expand Down Expand Up @@ -245,7 +253,14 @@ public Dos(DosOptions options, IMemory memory,
// Initialize memory manager first - it must know about the root COMMAND.COM reserved space
// Root PSP is at 0x60, environment at 0x68, so MCB chain starts after 0x6F
// This matches FreeDOS where DOS_PSP + 16 paragraphs is reserved
MemoryManager = new DosMemoryManager(_memory, initialPspSegment, loggerService);
bool umbEnabled = options.Umb && options.Xms is not false;
MemoryManager = new DosMemoryManager(_memory, initialPspSegment, loggerService,
DosSysVars, umbEnabled, options.Ems is not false);
if (options.Xms is not false) {
// XMS uses the DOS memory manager to allocate upper memory blocks.
Xms = new ExtendedMemoryManager(memory, state, a20Gate, memoryAsmWriter,
DosTables, MemoryManager, loggerService);
}

FcbManager = new(_memory, FileManager, DosDriveManager, _loggerService);
IBatchDisplayCommandHandler batchDisplayCommandHandler = new DosBatchDisplayCommandHandler(_vgaFunctionality);
Expand All @@ -261,7 +276,7 @@ public Dos(DosOptions options, IMemory memory,
DosInt20Handler = new DosInt20Handler(_memory, functionHandlerProvider, stack, state, DosInt21Handler, _loggerService);
DosInt2aHandler = new DosInt2aHandler(_memory, functionHandlerProvider, stack, state, _loggerService);
DosInt2FHandler = new DosInt2fHandler(_memory,
functionHandlerProvider, stack, state, _loggerService, _mscdex, xms);
functionHandlerProvider, stack, state, _loggerService, _mscdex, Xms);
DosInt25Handler = new DosDiskInt25Handler(_memory, DosDriveManager,
functionHandlerProvider, stack, state, floppyDiskTimingService, _loggerService);
DosInt26Handler = new DosDiskInt26Handler(_memory, DosDriveManager,
Expand All @@ -279,13 +294,11 @@ public Dos(DosOptions options, IMemory memory,
}
OpenDefaultFileHandles(dosDevices);

if (options.Xms is not false && xms is not null) {
Xms = xms;
AddDevice(xms, ExtendedMemoryManager.DosDeviceSegment, 0);
if (options.Xms is not false && Xms is not null) {
Comment thread
maximilien-noal marked this conversation as resolved.
Dismissed
AddDevice(Xms, ExtendedMemoryManager.DosDeviceSegment, 0);
}

if (options.Ems is not false) {
Ems = new(_memory, functionHandlerProvider, stack, state, _loggerService);
if (options.Ems is not false && Ems is not null) {
AddDevice(Ems.AsCharacterDevice(), ExpandedMemoryManager.DosDeviceSegment, 0);
}
}
Expand Down
Loading
Loading