Add auto-answering and auto-hanging functionality as well as the ability to control answering and hanging via a memory-mapped device to the emulated modem

This commit is contained in:
CrazyEttin 2022-10-05 20:23:26 +03:00
parent e626369a16
commit 64ddf705ea
3 changed files with 279 additions and 167 deletions

View File

@ -26,8 +26,9 @@ type
//Modem connection state //Modem connection state
type type
Connection = record Connection = record
Call: boolean; Originate: boolean;
Answer: boolean; Answer: boolean;
Dial: boolean;
Addr: longword; Addr: longword;
Port: word; Port: word;
Hang: boolean; Hang: boolean;
@ -51,7 +52,7 @@ const
{$endif} {$endif}
var var
Hlt, Echo{$ifdef floppy}, DiscRead, DiscWrite, DiscTrackSet, DiscSectSet{$endif}{$ifdef modem}, Calling, Answering{$endif}: boolean; //Halt and echo flags, disc system flags, and modem connection flags Hlt, Echo{$ifdef floppy}, DiscRead, DiscWrite, DiscTrackSet, DiscSectSet{$endif}{$ifdef modem}, Listening, Connected{$endif}: boolean; //Halt and echo flags, disc system flags, and modem connection flags
Op, Regs: 0 .. $f; //Opcode Op, Regs: 0 .. $f; //Opcode
X, Y: 0 .. 3; //Register arguments X, Y: 0 .. 3; //Register arguments
Addr, IP, RP: word; //Immediate or address argument and instruction and return pointers Addr, IP, RP: word; //Immediate or address argument and instruction and return pointers
@ -77,8 +78,9 @@ var
DiscDrive: 0 .. 1; //Current disc drive number DiscDrive: 0 .. 1; //Current disc drive number
{$endif} {$endif}
{$ifdef modem} {$ifdef modem}
ModemConn: Connection; //State of the modem ConnVar: Connection; //State of the modem
ModemState: file of Connection; //File storing the state of the modem ConnFile: file of Connection; //File storing the state of the modem
Mode: (Originate, Answer); //Modem mode
ServerSocket, ListenSocket, ClientSocket, ClientAddrSize: longint; //Server socket ServerSocket, ListenSocket, ClientSocket, ClientAddrSize: longint; //Server socket
ServerAddr, ClientAddr: TInetSockAddr; //Server address ServerAddr, ClientAddr: TInetSockAddr; //Server address
SigPipeHandler: pSigActionRec; //SIGPIPE handler SigPipeHandler: pSigActionRec; //SIGPIPE handler
@ -128,98 +130,116 @@ end;
{$endif} {$endif}
{$ifdef modem} {$ifdef modem}
//Connect //Check the modem state
procedure CallServer; procedure CheckModem;
begin begin
assign (ModemState, ExpandFileName ('~/.thingamajig/connection')); assign (ConnFile, ExpandFileName ('~/.thingamajig/connection'));
//Check the modem state //Check the modem state
if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin
try try
reset (ModemState); reset (ConnFile);
read (ModemState, ModemConn); read (ConnFile, ConnVar);
close (ModemState); close (ConnFile);
except except
end; end;
end; end;
//Hang //Auto-set things when dialing
if ModemConn.Hang then begin if ConnVar.Dial then begin
if Calling then begin //Auto-change to originate mode if dialing in answer mode
CloseSocket (ServerSocket); if Mode = Answer then ConnVar.Originate := true;
ModemConn.Hang := false; //Auto-hang if dialing
Calling := false; if Mode = Originate then if Connected then ConnVar.Hang := true;
end
else if Answering then begin
CloseSocket (ClientSocket);
CloseSocket (ListenSocket);
ModemConn.Hang := false;
Answering := false;
end;
end
else if ModemConn.Call then begin
if Calling then begin
CloseSocket (ServerSocket);
Calling := false;
end
else if Answering then begin
CloseSocket (ClientSocket);
CloseSocket (ListenSocket);
Answering := false;
end;
end
else if ModemConn.Answer then begin
if Calling then begin
CloseSocket (ServerSocket);
Calling := false;
end
else if Answering then begin
CloseSocket (ClientSocket);
CloseSocket (ListenSocket);
Answering := false;
end;
end; end;
//Call //Mode change
if ModemConn.Call then if Calling = false then begin //Originate
//Create a socket if ConnVar.Originate then begin
ServerSocket := fpSocket (AF_INET, SOCK_STREAM, 0); //Change the mode
if ServerSocket <> -1 then begin if Mode = Originate then begin
//Call if Connected then begin
CloseSocket (ServerSocket);
Connected := false;
end;
end
else if Mode = Answer then begin
if Connected then begin
CloseSocket (ClientSocket);
Connected := false;
end;
CloseSocket (ListenSocket);
Listening := false;
Mode := Originate;
end;
ConnVar.Originate := false;
end
//Answer
else if ConnVar.Answer then begin
//Change the mode
if Mode = Originate then begin
if Connected then begin
CloseSocket (ServerSocket);
Connected := false;
end;
Mode := Answer;
end
else if Mode = Answer then begin
if Connected then begin
CloseSocket (ClientSocket);
Connected := false;
end;
CloseSocket (ListenSocket);
Listening := false;
end;
//Create a listening socket
ListenSocket := fpSocket (AF_INET, SOCK_STREAM, 0);
if ListenSocket <> -1 then begin
ServerAddr.sin_family := AF_INET; ServerAddr.sin_family := AF_INET;
ServerAddr.sin_addr.s_addr := htonl (ModemConn.Addr); ServerAddr.sin_addr.s_addr := htonl (ConnVar.Addr);
ServerAddr.sin_port := htons (ModemConn.Port); ServerAddr.sin_port := htons (ConnVar.Port);
if fpConnect (ServerSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin if fpBind (ListenSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin
ModemConn.Call := false; if fpListen (ListenSocket, 1) <> -1 then begin
Calling := true; ConnVar.Answer := false;
Listening := true;
end;
end; end;
end; end;
end; end;
//Listen and answer //Hang
if ModemConn.Answer then if Answering = false then begin if ConnVar.Hang then begin
//Create a socket if Mode = Originate then begin
ListenSocket := fpSocket (AF_INET, SOCK_STREAM, 0); if Connected then begin
if ListenSocket <> -1 then begin CloseSocket (ServerSocket);
//Listen Connected := false;
end;
end
else if Mode = Answer then begin
if Connected then begin
CloseSocket (ClientSocket);
Connected := false;
end;
end;
ConnVar.Hang := false;
end;
//Dial
if ConnVar.Dial then if Connected = false then begin
//Create a server socket
ServerSocket := fpSocket (AF_INET, SOCK_STREAM, 0);
if ServerSocket <> -1 then begin
//Connect
ServerAddr.sin_family := AF_INET; ServerAddr.sin_family := AF_INET;
ServerAddr.sin_addr.s_addr := htonl (ModemConn.Addr); ServerAddr.sin_addr.s_addr := htonl (ConnVar.Addr);
ServerAddr.sin_port := htons (ModemConn.Port); ServerAddr.sin_port := htons (ConnVar.Port);
if fpBind (ListenSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin if fpConnect (ServerSocket, @ServerAddr, Sizeof (ServerAddr)) <> -1 then begin
if fpListen (ListenSocket, 1) <> -1 then begin ConnVar.Dial := false;
//Answer Connected := true;
ClientAddrSize := sizeof (ClientAddr);
ClientSocket := fpAccept (ListenSocket, @ClientAddr, @ClientAddrSize) ;
if ClientSocket <> -1 then begin
ModemConn.Answer := false;
Answering := true;
end;
end;
end; end;
end; end;
end; end;
//Save the modem state //Save the modem state
if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin if FileExists (ExpandFileName ('~/.thingamajig/connection')) then begin
try try
rewrite (ModemState); rewrite (ConnFile);
write (ModemState, ModemConn); write (ConnFile, ConnVar);
close (ModemState); close (ConnFile);
except except
end; end;
end; end;
@ -316,21 +336,44 @@ begin
{$endif} {$endif}
{$ifdef modem} {$ifdef modem}
//Modem //Modem
//Data
else if W = $fffa then begin else if W = $fffa then begin
{$ifndef fast} {$ifndef fast}
wait (33); wait (33);
{$endif} {$endif}
//Connect //Check the modem state
CallServer; CheckModem;
//Recieve //Recieve
if Calling then begin if Mode = Originate then begin
if fpRecv (ServerSocket, @B, 1, 0) <> 1 then B := 0; if Connected then begin
if fpRecv (ServerSocket, @B, 1, 0) <> 1 then begin
CloseSocket (ServerSocket);
Connected := false;
B := 0;
end;
end
else B := 0;
end end
else if Answering then begin else if Mode = Answer then begin
if fpRecv (ClientSocket, @B, 1, 0) <> 1 then B := 0; if Connected then begin
if fpRecv (ClientSocket, @B, 1, 0) <> 1 then begin
CloseSocket (ClientSocket);
Connected := false;
B := 0;
end;
end
else B := 0;
end end
else B := 0; else B := 0;
end end
//Status
else if W = $fff9 then begin
//Check the modem state
CheckModem;
//Load the status
if Connected then B := 1
else B := 0;
end
{$endif} {$endif}
{$ifdef status} {$ifdef status}
//Input status register //Input status register
@ -354,20 +397,25 @@ begin
B := B or $10; B := B or $10;
//FFFA: Modem or no input //FFFA: Modem or no input
{$ifdef modem} {$ifdef modem}
//Connect //Check the modem state
CallServer; CheckModem;
//Check connection status //Check connection status
if Calling then begin if Mode = Originate then begin
fpfd_zero (FileDescs); if Connected then begin
fpfd_set (ServerSocket, FileDescs); fpfd_zero (FileDescs);
if fpSelect (ServerSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20; fpfd_set (ServerSocket, FileDescs);
if fpSelect (ServerSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20;
end
else B := B or $20;
end end
else if Answering then begin else if Mode = Answer then begin
fpfd_zero (FileDescs); if Connected then begin
fpfd_set (ClientSocket, FileDescs); fpfd_zero (FileDescs);
if fpSelect (ClientSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20; fpfd_set (ClientSocket, FileDescs);
end if fpSelect (ClientSocket + 1, @FileDescs, nil, nil, 0) > 0 then B := B or $20;
else B := B or $20; end
else B := B or $20;
end;
{$endif} {$endif}
{$ifndef modem} {$ifndef modem}
B := B or $20; B := B or $20;
@ -717,15 +765,56 @@ begin
{$endif} {$endif}
{$ifdef modem} {$ifdef modem}
//Modem //Modem
//Data
else if W = $fffa then begin else if W = $fffa then begin
{$ifndef fast} {$ifndef fast}
wait (33); wait (33);
{$endif} {$endif}
//Connect //Check the modem state
CallServer; CheckModem;
//Send //Send
if Calling then fpSend (ServerSocket, @B, 1, 0) if Mode = Originate then begin
else if Answering then fpSend (ClientSocket, @B, 1, 0); if Connected then begin
if fpSend (ServerSocket, @B, 1, 0) <> 1 then begin
CloseSocket (ServerSocket);
Connected := false;
end;
end;
end
else if Mode = Answer then begin
if Connected then begin
if fpSend (ClientSocket, @B, 1, 0) <> 1 then begin
CloseSocket (ClientSocket);
Connected := false;
end;
end;
end;
end
//Status
else if W = $fff9 then begin
//Check the modem state
CheckModem;
//Change the status
if Mode = Originate then begin
if Connected then if (B and 1) = 0 then begin
CloseSocket (ServerSocket);
Connected := false;
end;
end
else if Mode = Answer then begin
if Connected then begin
CloseSocket (ClientSocket);
Connected := false;
end;
if Listening then if (B and 1) = 1 then begin
//Connect
ClientAddrSize := sizeof (ClientAddr);
ClientSocket := fpAccept (ListenSocket, @ClientAddr, @ClientAddrSize) ;
if ClientSocket <> -1 then begin
Connected := true;
end;
end;
end;
end end
{$endif} {$endif}
//Regular store //Regular store
@ -780,8 +869,9 @@ begin
{$ifdef modem} {$ifdef modem}
//Initialise the modem //Initialise the modem
Calling := false; Mode := Originate;
Answering := false; Listening := false;
Connected := false;
//Initialise the SIGPIPE handler //Initialise the SIGPIPE handler
new (SigPipeHandler); new (SigPipeHandler);
SigPipeHandler^.sa_Handler := SigActionHandler (@DoSig); SigPipeHandler^.sa_Handler := SigActionHandler (@DoSig);
@ -957,8 +1047,8 @@ begin
{$ifdef modem} {$ifdef modem}
//Disconnect the modem //Disconnect the modem
if Calling then CloseSocket (ServerSocket) if Mode = Originate then if Connected then CloseSocket (ServerSocket);
else if Answering then begin if Mode = Answer then if Connected then begin
CloseSocket (ClientSocket); CloseSocket (ClientSocket);
CloseSocket (ListenSocket); CloseSocket (ListenSocket);
end; end;

View File

@ -7,98 +7,110 @@ uses SysUtils, StrUtils, Sockets;
type type
//Connection state //Connection state
Connection = record Connection = record
Call: boolean; Originate: boolean;
Answer: boolean; Answer: boolean;
Dial: boolean;
Addr: longword; Addr: longword;
Port: word; Port: word;
Hang: boolean; Hang: boolean;
end; end;
var var
ModemConn: Connection; //State of the modem ConnVar: Connection; //State of the modem
State: file of Connection; //File storing the state of the modem ConnFile: file of Connection; //File storing the state of the modem
Addr: in_addr; //Temporary variable Addr: in_addr; //Temporary variable
begin begin
//Check the arguments //Check the arguments
if ParamStr (1) = '-c' then begin if ParamStr (1) = '-a' then begin
if ParamCount <> 2 then begin if ParamCount <> 2 then begin
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
end end
else if ParamStr (1) = '-a' then begin else if ParamStr (1) = '-d' then begin
if ParamCount <> 2 then begin if ParamCount <> 2 then begin
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
end end
else begin else begin
if ParamCount > 1 then begin if ParamCount > 1 then begin
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
end; end;
//Assign the state file //Assign the state file
assign (State, ExpandFileName ('~/.thingamajig/connection')); assign (ConnFile, ExpandFileName ('~/.thingamajig/connection'));
//Set the connection //Set the connection
if ParamStr (1) = '-c' then begin if ParamStr (1) = '-o' then begin
ModemConn.Call := true; ConnVar.Originate := true;
ModemConn.Answer := false; ConnVar.Answer := false;
try ConnVar.Dial := false;
Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':'])); ConnVar.Addr := 0;
ModemConn.Addr := Addr.s_addr; ConnVar.Port := 0;
except ConnVar.Hang := false;
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); end
halt (1);
end;
try
ModemConn.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':']));
except
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h');
halt (1);
end;
ModemConn.Hang := false;
end
else if ParamStr (1) = '-a' then begin else if ParamStr (1) = '-a' then begin
ModemConn.Call := false; ConnVar.Originate := false;
ModemConn.Answer := true; ConnVar.Answer := true;
ConnVar.Dial := false;
try try
Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':'])); Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':']));
ModemConn.Addr := Addr.s_addr; ConnVar.Addr := Addr.s_addr;
except except
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
try try
ModemConn.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':'])); ConnVar.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':']));
except except
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
ModemConn.Hang := false; ConnVar.Hang := false;
ModemConn.Hang := false; ConnVar.Hang := false;
end
else if ParamStr (1) = '-d' then begin
ConnVar.Originate := false;
ConnVar.Answer := false;
ConnVar.Dial := true;
try
Addr := StrToHostAddr (ExtractDelimited (1, ParamStr (2), [':']));
ConnVar.Addr := Addr.s_addr;
except
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1);
end;
try
ConnVar.Port := StrToInt (ExtractDelimited (2, ParamStr (2), [':']));
except
writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1);
end;
ConnVar.Hang := false;
end end
else if ParamStr (1) = '-h' then begin else if ParamStr (1) = '-h' then begin
ModemConn.Call := false; ConnVar.Originate := false;
ModemConn.Answer := false; ConnVar.Answer := false;
ModemConn.Addr := 0; ConnVar.Dial := false;
ModemConn.Port := 0; ConnVar.Addr := 0;
ModemConn.Hang := true; ConnVar.Port := 0;
ConnVar.Hang := true;
end end
else begin else begin
writeln ('Usage: modemctl [-c address:port]/[-a address:port]/-h'); writeln ('Usage: modemctl -o/[-a address:port]/[-d address:port]/-h');
halt (1); halt (1);
end; end;
//Write the state //Write the state
try try
rewrite (State); rewrite (ConnFile);
write (State, ModemConn); write (ConnFile, ConnVar);
close (State); close (ConnFile);
except except
writeln ('Error: could not set the connection'); writeln ('Error: could not set the connection');
end; end;

View File

@ -98,7 +98,7 @@ Usage:
* emulator (-v) program (2> verbose_output) * emulator (-v) program (2> verbose_output)
* tapectl (-r tape) (-p tape) * tapectl (-r tape) (-p tape)
* floppyctl (-0 disc) (-1 disc) * floppyctl (-0 disc) (-1 disc)
* modemctl [-c address:port]/[-a address:port]/-h * modemctl -o/[-a address:port]/[-d address:port]/-h
By default the emulator runs at roughly 500 KIPS and has 2 KiB of RAM. By default the emulator runs at roughly 500 KIPS and has 2 KiB of RAM.
The arguments -dRAM4, -dRAM8, -dRAM16, -dRAM32, and -dRAM64 can be used The arguments -dRAM4, -dRAM8, -dRAM16, -dRAM32, and -dRAM64 can be used
@ -118,25 +118,29 @@ standard uses. The backspace and delete keys input their respective
characters and non-character keys null. characters and non-character keys null.
In Linux the emulator can be compiled with support for a character In Linux the emulator can be compiled with support for a character
printer, an emulated high speed (roughly 500 CPS in and 50 CPS out) printer, an emulated high speed 8-bit paper tape reader and punch, an
8-bit paper tape reader and punch, an emulated two-drive 8" floppy disc emulated two-drive 8" floppy disc system, a modem, and an input status
system, a roughly 300 b/s modem, and an input status register with the register with the arguments -dprinter, -dtape, -dfloppy, -dmodem, and
arguments -dprinter, -dtape, -dfloppy, -dmodem, and -dstatus -dstatus respectively. Full 64 KiB of RAM and all of these options can
respectively. Full 64 KiB of RAM and all of these options can also be also be enabled with the argument -dfull. The printer is mapped to
enabled with the argument -dfull. The printer is mapped to address FFFE, address FFFE, the tape reader and punch to FFFD, the disc system to FFFB
the tape reader and punch to FFFD, the disc system to FFFB and FFFC, the and FFFC, the modem to FFF9 and FFFA, and the input status register to
modem to FFFA, and the input status register to FFF8. The printer prints FFF8.
into /dev/usb/lp0. The tape files read from and punched to are (re)set
using the program tapectl with the arguments -r and -p respectively and The printer prints into /dev/usb/lp0. The tape files read from and
the disc files in drives 0 and 1 using the program floppyctl with the punched to are (re)set using the program tapectl with the arguments -r
arguments -0 and -1 respectively. The disc system uses hard sectored and -p respectively and the disc files in drives 0 and 1 using the
single-sided discs with 77 tracks of 32 sectors of 137 bytes, or 337568 program floppyctl with the arguments -0 and -1 respectively. The disc
bytes in total: the disc files must be of this size. The modem is system uses hard sectored single-sided discs with 77 tracks of 32
controlled by the program modemctl: the option -c is used to call an IP sectors of 137 bytes, or 337568 bytes in total: the disc files must be
address and port, -a to get ready for answering a call to a loopback or of this size. The modem is controlled by the program modemctl: the
local IP address and port, and -h to hang. Hanging manually is not options -o and -a are used to set the originate and answer modes, the
necessary when making or getting ready to answer a new call, switching latter with the address and port to be listened; -d to dial an address
between calling and answering, or shutting down the emulator. and port to be called; and -h to hang. Dialing when in answer mode
automatically switches the modem to originate mode. The modem hangs
automatically if the connection is lost or hanged from the other side,
if another address and port are dialed, if a mode change command is
given, or if the emulator is halted.
The floppy disc system uses two ports: command at FFFB and data at FFFC. The floppy disc system uses two ports: command at FFFB and data at FFFC.
Only the low nibble of the command port is used, consisting of a Only the low nibble of the command port is used, consisting of a
@ -158,6 +162,12 @@ The commands for the disc system are:
6: Read a sector from a disc to the buffer. 6: Read a sector from a disc to the buffer.
7: Write a sector from the buffer to a disc. 7: Write a sector from the buffer to a disc.
The modem also uses two ports: status at FFF9 and data at FFFA. Only the
least significant bit of the status port is used, indicating whether
the modem is connected or not. Unsetting the bit hangs the modem while
setting it in answer mode listens for an incoming connection to answer.
Setting the bit in originate mode is ignored.
The input status register can be used to check if a device is ready to The input status register can be used to check if a device is ready to
be read from. The reserved addresses FFF8-FFFF are mapped to the bits be read from. The reserved addresses FFF8-FFFF are mapped to the bits
7-0: a set bit means either that the device is ready or that no input 7-0: a set bit means either that the device is ready or that no input