This commit is contained in:
Benjamin Rosseaux 2026-06-04 04:28:52 +02:00
commit ea9f0217a1
3 changed files with 229 additions and 4 deletions

View file

@ -137,6 +137,7 @@ A RISC-V RV64GCV/RVA23 emulator written in Object Pascal. It simulates processor
- Filesystem (virtio-fs, FUSE-based host directory sharing)
- Keyboard
- Mouse
- Gamepad
- Sound
- VirtIO Sound (default)
- FM801 (Ensoniq PCI sound card, with PCM and OPL3 support, if SoundMode=FM801, but currently with some timing dropout issues, so CMI8738 is recommended instead if OPL3 is needed, otherwise just use VirtIO Sound which works fine and has better performance and lower latency than all hardware-emulated sound options)

View file

@ -79,6 +79,7 @@ are required. The VirtIO devices are used to provide a standard interface for de
| VIRTIO FS | $1005b000 | $1000 | $1b | VirtIO filesystem device |
| VIRTIO CRYPTO | $1005c000 | $1000 | $1c | VirtIO crypto device |
| VIRTIO BALLOON | $1005d000 | $1000 | $1d | VirtIO balloon device |
| VIRTIO INPUT GAMEPAD | $1005e000 | $1000 | $1e | VirtIO gamepad input (evdev: BTN_GAMEPAD + dual sticks/triggers/D-pad) |
For each VirtIO device, the MMIO region size is $1000. For the VirtIO GPU device, the guest OS allocates its own frame buffer memory ranges, so $1000 as MMIO region size remains valid for the device registers in this case.

View file

@ -1,7 +1,7 @@
(******************************************************************************
* PasRISCV *
******************************************************************************
* Version 2026-05-13-03-23-0000 *
* Version 2026-06-04-04-25-0000 *
******************************************************************************
* zlib license *
*============================================================================*
@ -6452,14 +6452,42 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
ABS_X=$00;
ABS_Y=$01;
ABS_Z=$02;
ABS_RX=$03;
ABS_RY=$04;
ABS_RZ=$05;
ABS_HAT0X=$10;
ABS_HAT0Y=$11;
BTN_SOUTH=$130; // A
BTN_EAST=$131; // B
BTN_NORTH=$133; // X
BTN_WEST=$134; // Y
BTN_TL=$136; // Left shoulder
BTN_TR=$137; // Right shoulder
BTN_TL2=$138; // Left trigger (digital)
BTN_TR2=$139; // Right trigger (digital)
BTN_SELECT=$13a;
BTN_START=$13b;
BTN_MODE=$13c; // Guide/Home
BTN_THUMBL=$13d; // Left stick press
BTN_THUMBR=$13e; // Right stick press
GAMEPAD_ABS_MIN=-32767;
GAMEPAD_ABS_MAX=32767;
GAMEPAD_TRIGGER_MAX=32767;
ButtonList:array[0..2] of TPasRISCVUInt16=(BTN_LEFT,BTN_RIGHT,BTN_MIDDLE);
GamepadButtonList:array[0..12] of TPasRISCVUInt16=
(
BTN_SOUTH,BTN_EAST,BTN_NORTH,BTN_WEST,BTN_TL,BTN_TR,BTN_TL2,BTN_TR2,
BTN_SELECT,BTN_START,BTN_MODE,BTN_THUMBL,BTN_THUMBR
);
GamepadAxisList:array[0..7] of TPasRISCVUInt16=(ABS_X,ABS_Y,ABS_RX,ABS_RY,ABS_Z,ABS_RZ,ABS_HAT0X,ABS_HAT0Y);
Vendor=$ffff;
DeviceID=$12;
type TInputKind=
(
Keyboard,
Mouse,
Tablet
Tablet,
Gamepad
);
TKind=TInputKind;
PKind=^TKind;
@ -6471,6 +6499,7 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
destructor Destroy; override;
function HandleKeyboard(const aKeyCode:TPasRISCVUInt16;const aDown:Boolean):Boolean;
function HandleMouse(const aDX,aDY,aDZ:TPasRISCVInt32;const aButtons:TPasRISCVUInt32):Boolean;
function HandleGamepad(const aLeftX,aLeftY,aRightX,aRightY,aLeftTrigger,aRightTrigger,aHatX,aHatY:TPasRISCVInt32;const aButtons:TPasRISCVUInt32):Boolean;
procedure DeviceReset; override;
function DeviceRecv(const aQueueIndex,aDescriptorIndex,aReadSize,aWriteSize:TPasRISCVUInt64):Boolean; override;
function QueueEvent(const aType,aCode:TPasRISCVUInt16;const aValue:TPasRISCVUInt32):Boolean;
@ -6511,6 +6540,17 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
procedure LoadSnapshot(const aNode:TSnapshot.TNode); override;
procedure SaveSnapshot(const aNode:TSnapshot.TNode); override;
end;
TVirtIOInputGamepadDevice=class(TVirtIOInputDevice)
public
const DefaultBaseAddress=TPasRISCVUInt64($1005e000);
DefaultSize=TPasRISCVUInt64($1000);
DefaultIRQ=TPasRISCVUInt64($1e);
public
constructor Create(const aMachine:TPasRISCV); reintroduce;
procedure DeviceInitialize; override;
procedure LoadSnapshot(const aNode:TSnapshot.TNode); override;
procedure SaveSnapshot(const aNode:TSnapshot.TNode); override;
end;
{ TVirtIOSoundDevice }
TVirtIOSoundDevice=class(TVirtIODevice)
public
@ -13392,6 +13432,10 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
fVirtIOInputTabletSize:TPasRISCVUInt64;
fVirtIOInputTabletIRQ:TPasRISCVUInt64;
fVirtIOInputGamepadBase:TPasRISCVUInt64;
fVirtIOInputGamepadSize:TPasRISCVUInt64;
fVirtIOInputGamepadIRQ:TPasRISCVUInt64;
fVirtIOSoundBase:TPasRISCVUInt64;
fVirtIOSoundSize:TPasRISCVUInt64;
fVirtIOSoundIRQ:TPasRISCVUInt64;
@ -13611,6 +13655,10 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
property VirtIOInputTabletSize:TPasRISCVUInt64 read fVirtIOInputTabletSize write fVirtIOInputTabletSize;
property VirtIOInputTabletIRQ:TPasRISCVUInt64 read fVirtIOInputTabletIRQ write fVirtIOInputTabletIRQ;
property VirtIOInputGamepadBase:TPasRISCVUInt64 read fVirtIOInputGamepadBase write fVirtIOInputGamepadBase;
property VirtIOInputGamepadSize:TPasRISCVUInt64 read fVirtIOInputGamepadSize write fVirtIOInputGamepadSize;
property VirtIOInputGamepadIRQ:TPasRISCVUInt64 read fVirtIOInputGamepadIRQ write fVirtIOInputGamepadIRQ;
property VirtIOSoundBase:TPasRISCVUInt64 read fVirtIOSoundBase write fVirtIOSoundBase;
property VirtIOSoundSize:TPasRISCVUInt64 read fVirtIOSoundSize write fVirtIOSoundSize;
property VirtIOSoundIRQ:TPasRISCVUInt64 read fVirtIOSoundIRQ write fVirtIOSoundIRQ;
@ -13813,6 +13861,8 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
fVirtIOInputTabletDevice:TVirtIOInputTabletDevice;
fVirtIOInputGamepadDevice:TVirtIOInputGamepadDevice;
fVirtIOSoundDevice:TVirtIOSoundDevice;
fVirtIO9PDevice:TVirtIO9PDevice;
@ -14063,6 +14113,8 @@ type PPPasRISCVInt8=^PPasRISCVInt8;
property VirtIOInputTabletDevice:TVirtIOInputTabletDevice read fVirtIOInputTabletDevice;
property VirtIOInputGamepadDevice:TVirtIOInputGamepadDevice read fVirtIOInputGamepadDevice;
property VirtIOSoundDevice:TVirtIOSoundDevice read fVirtIOSoundDevice;
property VirtIO9PDevice:TVirtIO9PDevice read fVirtIO9PDevice;
@ -55805,9 +55857,54 @@ begin
end;
end;
function TPasRISCV.TVirtIOInputDevice.HandleGamepad(const aLeftX,aLeftY,aRightX,aRightY,aLeftTrigger,aRightTrigger,aHatX,aHatY:TPasRISCVInt32;const aButtons:TPasRISCVUInt32):Boolean;
var Index:TPasRISCVSizeInt;
Button,LastButton:Boolean;
Axes:array[0..7] of TPasRISCVInt32;
begin
if fKind=TPasRISCV.TVirtIOInputDevice.TKind.Gamepad then begin
Axes[0]:=aLeftX;
Axes[1]:=aLeftY;
Axes[2]:=aRightX;
Axes[3]:=aRightY;
Axes[4]:=aLeftTrigger;
Axes[5]:=aRightTrigger;
Axes[6]:=aHatX;
Axes[7]:=aHatY;
result:=true;
// Emit absolute axis state (evdev filters out unchanged ABS values on the guest side)
for Index:=0 to length(GamepadAxisList)-1 do begin
result:=QueueEvent(VIRTIO_INPUT_EV_ABS,GamepadAxisList[Index],TPasRISCVUInt32(Axes[Index]));
if not result then begin
break;
end;
end;
// Emit only the buttons that changed since the last report
if result and (aButtons<>fButtonState) then begin
for Index:=0 to length(GamepadButtonList)-1 do begin
Button:=(aButtons and (TPasRISCVUInt32(1) shl Index))<>0;
LastButton:=(fButtonState and (TPasRISCVUInt32(1) shl Index))<>0;
if Button<>LastButton then begin
result:=QueueEvent(VIRTIO_INPUT_EV_KEY,GamepadButtonList[Index],ord(Button) and 1);
if not result then begin
break;
end;
end;
end;
fButtonState:=aButtons;
end;
if result then begin
result:=QueueEvent(VIRTIO_INPUT_EV_SYN,0,0);
end;
end else begin
result:=false;
end;
end;
procedure TPasRISCV.TVirtIOInputDevice.DeviceConfigWrite;
var Name:TPasRISCVRawByteString;
Index,Len:TPasRISCVSizeInt;
AbsMin,AbsMax,AbsFuzz,AbsFlat,AbsRes:TPasRISCVInt32;
begin
case fConfigSpace[0] of
VIRTIO_INPUT_CFG_UNSET:begin
@ -55823,6 +55920,9 @@ begin
TPasRISCV.TVirtIOInputDevice.TKind.Tablet:begin
Name:='virtio_tablet';
end;
TPasRISCV.TVirtIOInputDevice.TKind.Gamepad:begin
Name:='virtio_gamepad';
end;
else begin
Name:='';
end;
@ -55899,6 +55999,28 @@ begin
end;
end;
end;
TPasRISCV.TVirtIOInputDevice.TKind.Gamepad:begin
case fConfigSpace[1] of
VIRTIO_INPUT_EV_KEY:begin
fConfigSpace[2]:=768 div 8;
FillChar(fConfigSpace[8],768 div 8,#0);
for Index:=0 to length(GamepadButtonList)-1 do begin
fConfigSpace[8+(GamepadButtonList[Index] shr 3)]:=fConfigSpace[8+(GamepadButtonList[Index] shr 3)] or (1 shl (GamepadButtonList[Index] and 7));
end;
end;
VIRTIO_INPUT_EV_ABS:begin
// Bits up to ABS_HAT0Y ($11) must be representable, so report 4 bytes
fConfigSpace[2]:=4;
PPasRISCVUInt32(Pointer(@fConfigSpace[8]))^:=(1 shl ABS_X) or (1 shl ABS_Y) or
(1 shl ABS_RX) or (1 shl ABS_RY) or
(1 shl ABS_Z) or (1 shl ABS_RZ) or
(1 shl ABS_HAT0X) or (1 shl ABS_HAT0Y);
end;
else begin
fConfigSpace[2]:=0;
end;
end;
end;
else begin
fConfigSpace[2]:=0;
end;
@ -55912,6 +56034,41 @@ begin
PPasRISCVUInt32(Pointer(@fConfigSpace[16]))^:=0; // fuzz
PPasRISCVUInt32(Pointer(@fConfigSpace[20]))^:=0; // flat
PPasRISCVUInt32(Pointer(@fConfigSpace[24]))^:=0; // res
end else if fKind=TPasRISCV.TVirtIOInputDevice.TKind.Gamepad then begin
AbsMin:=0;
AbsMax:=0;
AbsFuzz:=0;
AbsFlat:=0;
AbsRes:=0;
case fConfigSpace[1] of
ABS_X,ABS_Y,ABS_RX,ABS_RY:begin // Analog sticks, centered signed range
AbsMin:=GAMEPAD_ABS_MIN;
AbsMax:=GAMEPAD_ABS_MAX;
AbsFuzz:=16;
AbsFlat:=128;
end;
ABS_Z,ABS_RZ:begin // Triggers, unsigned range
AbsMin:=0;
AbsMax:=GAMEPAD_TRIGGER_MAX;
end;
ABS_HAT0X,ABS_HAT0Y:begin // D-pad, three-state
AbsMin:=-1;
AbsMax:=1;
end;
else begin
AbsMax:=-1; // Mark as unsupported axis below
end;
end;
if AbsMax<>-1 then begin
fConfigSpace[2]:=5*4;
PPasRISCVUInt32(Pointer(@fConfigSpace[8]))^:=TPasRISCVUInt32(AbsMin);
PPasRISCVUInt32(Pointer(@fConfigSpace[12]))^:=TPasRISCVUInt32(AbsMax);
PPasRISCVUInt32(Pointer(@fConfigSpace[16]))^:=TPasRISCVUInt32(AbsFuzz);
PPasRISCVUInt32(Pointer(@fConfigSpace[20]))^:=TPasRISCVUInt32(AbsFlat);
PPasRISCVUInt32(Pointer(@fConfigSpace[24]))^:=TPasRISCVUInt32(AbsRes);
end else begin
fConfigSpace[2]:=0;
end;
end;
end;
else begin
@ -55920,7 +56077,6 @@ begin
end;
end;
procedure TPasRISCV.TVirtIOInputDevice.LoadSnapshot(const aNode:TSnapshot.TNode);
begin
inherited LoadSnapshot(aNode);
@ -56011,6 +56167,28 @@ begin
inherited SaveSnapshot(aNode);
end;
{ TPasRISCV.TVirtIOInputGamepadDevice }
constructor TPasRISCV.TVirtIOInputGamepadDevice.Create(const aMachine:TPasRISCV);
begin
inherited Create(aMachine,aMachine.fConfiguration.fVirtIOInputGamepadBase,aMachine.fConfiguration.fVirtIOInputGamepadSize,TVirtIOInputDevice.TKind.Gamepad);
fIRQ:=aMachine.fConfiguration.fVirtIOInputGamepadIRQ;
end;
procedure TPasRISCV.TVirtIOInputGamepadDevice.DeviceInitialize;
begin
end;
procedure TPasRISCV.TVirtIOInputGamepadDevice.LoadSnapshot(const aNode:TSnapshot.TNode);
begin
inherited LoadSnapshot(aNode);
end;
procedure TPasRISCV.TVirtIOInputGamepadDevice.SaveSnapshot(const aNode:TSnapshot.TNode);
begin
inherited SaveSnapshot(aNode);
end;
{ TPasRISCV.TVirtIOSoundDevice.TPCMBuffer }
constructor TPasRISCV.TVirtIOSoundDevice.TPCMBuffer.Create(const aDevice:TVirtIOSoundDevice);
@ -138263,6 +138441,10 @@ begin
fVirtIOInputTabletSize:=TPasRISCV.TVirtIOInputTabletDevice.DefaultSize;
fVirtIOInputTabletIRQ:=TPasRISCV.TVirtIOInputTabletDevice.DefaultIRQ;
fVirtIOInputGamepadBase:=TPasRISCV.TVirtIOInputGamepadDevice.DefaultBaseAddress;
fVirtIOInputGamepadSize:=TPasRISCV.TVirtIOInputGamepadDevice.DefaultSize;
fVirtIOInputGamepadIRQ:=TPasRISCV.TVirtIOInputGamepadDevice.DefaultIRQ;
fVirtIOSoundBase:=TPasRISCV.TVirtIOSoundDevice.DefaultBaseAddress;
fVirtIOSoundSize:=TPasRISCV.TVirtIOSoundDevice.DefaultSize;
fVirtIOSoundIRQ:=TPasRISCV.TVirtIOSoundDevice.DefaultIRQ;
@ -138477,6 +138659,10 @@ begin
fVirtIOInputTabletSize:=aConfiguration.fVirtIOInputTabletSize;
fVirtIOInputTabletIRQ:=aConfiguration.fVirtIOInputTabletIRQ;
fVirtIOInputGamepadBase:=aConfiguration.fVirtIOInputGamepadBase;
fVirtIOInputGamepadSize:=aConfiguration.fVirtIOInputGamepadSize;
fVirtIOInputGamepadIRQ:=aConfiguration.fVirtIOInputGamepadIRQ;
fVirtIOSoundBase:=aConfiguration.fVirtIOSoundBase;
fVirtIOSoundSize:=aConfiguration.fVirtIOSoundSize;
fVirtIOSoundIRQ:=aConfiguration.fVirtIOSoundIRQ;
@ -138760,6 +138946,9 @@ begin
fVirtIOInputTabletBase:=aNode.GetUInt64('VirtIOInputTabletBase',fVirtIOInputTabletBase);
fVirtIOInputTabletSize:=aNode.GetUInt64('VirtIOInputTabletSize',fVirtIOInputTabletSize);
fVirtIOInputTabletIRQ:=aNode.GetUInt64('VirtIOInputTabletIRQ',fVirtIOInputTabletIRQ);
fVirtIOInputGamepadBase:=aNode.GetUInt64('VirtIOInputGamepadBase',fVirtIOInputGamepadBase);
fVirtIOInputGamepadSize:=aNode.GetUInt64('VirtIOInputGamepadSize',fVirtIOInputGamepadSize);
fVirtIOInputGamepadIRQ:=aNode.GetUInt64('VirtIOInputGamepadIRQ',fVirtIOInputGamepadIRQ);
// VirtIO Sound
fVirtIOSoundBase:=aNode.GetUInt64('VirtIOSoundBase',fVirtIOSoundBase);
@ -138981,6 +139170,9 @@ begin
aNode.SetUInt64('VirtIOInputTabletBase',fVirtIOInputTabletBase);
aNode.SetUInt64('VirtIOInputTabletSize',fVirtIOInputTabletSize);
aNode.SetUInt64('VirtIOInputTabletIRQ',fVirtIOInputTabletIRQ);
aNode.SetUInt64('VirtIOInputGamepadBase',fVirtIOInputGamepadBase);
aNode.SetUInt64('VirtIOInputGamepadSize',fVirtIOInputGamepadSize);
aNode.SetUInt64('VirtIOInputGamepadIRQ',fVirtIOInputGamepadIRQ);
// VirtIO Sound
aNode.SetUInt64('VirtIOSoundBase',fVirtIOSoundBase);
@ -139410,6 +139602,8 @@ begin
fVirtIOInputTabletDevice:=TVirtIOInputTabletDevice.Create(self);
fVirtIOInputGamepadDevice:=TVirtIOInputGamepadDevice.Create(self);
fVirtIO9PDevice:=TVirtIO9PDevice.Create(self);
fVirtIONetDevice:=TVirtIONetDevice.Create(self);
@ -139560,6 +139754,7 @@ begin
fBus.AddBusDevice(fVirtIOInputKeyboardDevice);
fBus.AddBusDevice(fVirtIOInputMouseDevice);
fBus.AddBusDevice(fVirtIOInputTabletDevice);
fBus.AddBusDevice(fVirtIOInputGamepadDevice);
if assigned(fVirtIOSoundDevice) then begin
fBus.AddBusDevice(fVirtIOSoundDevice);
end;
@ -139790,6 +139985,7 @@ begin
FreeAndNil(fVirtIOInputKeyboardDevice);
FreeAndNil(fVirtIOInputMouseDevice);
FreeAndNil(fVirtIOInputTabletDevice);
FreeAndNil(fVirtIOInputGamepadDevice);
FreeAndNil(fVirtIOSoundDevice);
FreeAndNil(fVirtIO9PDevice);
if assigned(fEthernetDevice) then begin
@ -139921,7 +140117,7 @@ var Index,DeviceID,IRQPin:TPasRISCVSizeInt;
I2CClockNode,I2CNode,I2CHIDKeyboardNode,I2CDS1307Node,
PS2KeyboardNode,PS2MouseNode,
VirtIOBlockNode,
VirtIOInputKeyboardNode,VirtIOInputMouseNode,VirtIOInputTabletNode,VirtIOSoundNode,
VirtIOInputKeyboardNode,VirtIOInputMouseNode,VirtIOInputTabletNode,VirtIOInputGamepadNode,VirtIOSoundNode,
VirtIO9PNode,VirtIONetNode,
VirtIORandomGeneratorNode,
VirtIOGPUNode,
@ -140915,6 +141111,26 @@ begin
SoCNode.AddChild(VirtIOInputTabletNode);
end;
VirtIOInputGamepadNode:=TPasRISCV.TFDT.TFDTNode.Create(fFDT,'virtio',fConfiguration.fVirtIOInputGamepadBase);
try
VirtIOInputGamepadNode.AddPropertyString('compatible','virtio,mmio');
Cells[0]:=0;
Cells[1]:=fConfiguration.fVirtIOInputGamepadBase;
Cells[2]:=0;
Cells[3]:=fConfiguration.fVirtIOInputGamepadSize;
VirtIOInputGamepadNode.AddPropertyCells('reg',@Cells,4);
Cells[0]:=INTC0.GetPHandle;
Cells[1]:=TPasRISCVUInt32(fConfiguration.fVirtIOInputGamepadIRQ);
if fAIA then begin
Cells[2]:=4; // IRQ_TYPE_LEVEL_HIGH
VirtIOInputGamepadNode.AddPropertyCells('interrupts-extended',@Cells,3);
end else begin
VirtIOInputGamepadNode.AddPropertyCells('interrupts-extended',@Cells,2);
end;
finally
SoCNode.AddChild(VirtIOInputGamepadNode);
end;
if fConfiguration.fSoundMode=TSoundMode.VirtIO then begin
VirtIOSoundNode:=TPasRISCV.TFDT.TFDTNode.Create(fFDT,'virtio',fConfiguration.fVirtIOSoundBase);
try
@ -141815,6 +142031,7 @@ begin
fVirtIOInputKeyboardDevice.Reset;
fVirtIOInputMouseDevice.Reset;
fVirtIOInputTabletDevice.Reset;
fVirtIOInputGamepadDevice.Reset;
if assigned(fVirtIOSoundDevice) then begin
fVirtIOSoundDevice.Reset;
end;
@ -142830,6 +143047,9 @@ begin
if assigned(fVirtIOInputTabletDevice) then begin
fVirtIOInputTabletDevice.LoadSnapshot(Snapshot.Root.FindNode('VirtIOInputTablet'));
end;
if assigned(fVirtIOInputGamepadDevice) then begin
fVirtIOInputGamepadDevice.LoadSnapshot(Snapshot.Root.FindNode('VirtIOInputGamepad'));
end;
if assigned(fVirtIOSoundDevice) then begin
fVirtIOSoundDevice.LoadSnapshot(Snapshot.Root.FindNode('VirtIOSound'));
end;
@ -143015,6 +143235,9 @@ begin
if assigned(fVirtIOInputTabletDevice) then begin
fVirtIOInputTabletDevice.SaveSnapshot(Snapshot.Root.AddObject('VirtIOInputTablet'));
end;
if assigned(fVirtIOInputGamepadDevice) then begin
fVirtIOInputGamepadDevice.SaveSnapshot(Snapshot.Root.AddObject('VirtIOInputGamepad'));
end;
if assigned(fVirtIOSoundDevice) then begin
fVirtIOSoundDevice.SaveSnapshot(Snapshot.Root.AddObject('VirtIOSound'));
end;