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

View File

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

View File

@ -98,7 +98,7 @@ Usage:
* emulator (-v) program (2> verbose_output)
* tapectl (-r tape) (-p tape)
* 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.
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.
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)
8-bit paper tape reader and punch, an emulated two-drive 8" floppy disc
system, a roughly 300 b/s modem, and an input status register with the
arguments -dprinter, -dtape, -dfloppy, -dmodem, and -dstatus
respectively. Full 64 KiB of RAM and all of these options can also be
enabled with the argument -dfull. The printer is mapped to address FFFE,
the tape reader and punch to FFFD, the disc system to FFFB and FFFC, the
modem to FFFA, and the input status register to FFF8. The printer prints
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 disc files in drives 0 and 1 using the program floppyctl with the
arguments -0 and -1 respectively. The disc system uses hard sectored
single-sided discs with 77 tracks of 32 sectors of 137 bytes, or 337568
bytes in total: the disc files must be of this size. The modem is
controlled by the program modemctl: the option -c is used to call an IP
address and port, -a to get ready for answering a call to a loopback or
local IP address and port, and -h to hang. Hanging manually is not
necessary when making or getting ready to answer a new call, switching
between calling and answering, or shutting down the emulator.
printer, an emulated high speed 8-bit paper tape reader and punch, an
emulated two-drive 8" floppy disc system, a modem, and an input status
register with the arguments -dprinter, -dtape, -dfloppy, -dmodem, and
-dstatus respectively. Full 64 KiB of RAM and all of these options can
also be enabled with the argument -dfull. The printer is mapped to
address FFFE, the tape reader and punch to FFFD, the disc system to FFFB
and FFFC, the modem to FFF9 and FFFA, and the input status register to
FFF8.
The printer prints 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 disc files in drives 0 and 1 using the
program floppyctl with the arguments -0 and -1 respectively. The disc
system uses hard sectored single-sided discs with 77 tracks of 32
sectors of 137 bytes, or 337568 bytes in total: the disc files must be
of this size. The modem is controlled by the program modemctl: the
options -o and -a are used to set the originate and answer modes, the
latter with the address and port to be listened; -d to dial an address
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.
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.
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
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