From 64ddf705ea59006788cd6d2544b2ff09697bb07b Mon Sep 17 00:00:00 2001 From: CrazyEttin <> Date: Wed, 5 Oct 2022 20:23:26 +0300 Subject: [PATCH] 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 --- emulator.pas | 296 +++++++++++++++++++++++++++++++++------------------ modemctl.pas | 100 +++++++++-------- readme.md | 50 +++++---- 3 files changed, 279 insertions(+), 167 deletions(-) diff --git a/emulator.pas b/emulator.pas index 10cd334..c868d34 100644 --- a/emulator.pas +++ b/emulator.pas @@ -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; diff --git a/modemctl.pas b/modemctl.pas index 0a39259..0b2af94 100644 --- a/modemctl.pas +++ b/modemctl.pas @@ -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; diff --git a/readme.md b/readme.md index 181ca59..a6e4fcd 100644 --- a/readme.md +++ b/readme.md @@ -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