home *** CD-ROM | disk | FTP | other *** search
/ The Unsorted BBS Collection / thegreatunsorted.tar / thegreatunsorted / hacking / phreak_utils_pc / lscomm.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1995-04-01  |  17.8 KB  |  705 lines

  1. UNIT lscomm;
  2.  
  3. {Version 3.1    8/11/92 rob lerner}
  4.  
  5. {This unit is the communications port interrupt driver for the IBM-PC.
  6. It handles handles all low-level i/o through the serial port.  It is
  7. installed by calling Cominstall.  It deinstalls itself automatically
  8. when the program exits, or you can deinstall it by calling Comdeinstall.
  9.  
  10. Donated to the public domain by Wayne E. Conrad, January, 1989.
  11. If you have any problems or suggestions, please contact me at my BBS:
  12.  
  13.     Pascalaholics Anonymous
  14.     (602) 484-9356
  15.     2400 bps
  16.     The home of WBBS
  17.     Lots of source code
  18.  
  19. Modified on 8/11/92 for procedure names to be better aligned to function
  20. rob lerner
  21.  
  22. }
  23.  
  24. INTERFACE
  25.  
  26. USES
  27.   Dos;
  28.  
  29. CONST
  30.   ComInstalled: Boolean = False;
  31.  
  32. TYPE
  33.   Comparity = (ComNone, ComEven, ComOdd, ComZero, ComOne);
  34.  
  35.  
  36. PROCEDURE ComFlushRx;
  37. PROCEDURE ComFlushTx;
  38. FUNCTION  Comcarrier: Boolean;
  39. FUNCTION  ComRx: Char;
  40. FUNCTION  ComTxReady: Boolean;
  41. FUNCTION  ComTxEmpty: Boolean;
  42. FUNCTION  ComRxEmpty: Boolean;
  43. PROCEDURE ComTx (ch: Char);
  44. PROCEDURE ComRxString (st: String);
  45. PROCEDURE ComLowerDTR;
  46. PROCEDURE ComRaiseDTR;
  47. PROCEDURE ComSetSpeed (speed: Word);
  48. PROCEDURE ComSetParity (parity: Comparity; stop_bits: Byte);
  49. PROCEDURE ComInstall
  50.   (
  51.   portnum  : Word;
  52.   VAR error: Word
  53.   );
  54. PROCEDURE Comdeinstall;
  55.  
  56.  
  57. IMPLEMENTATION
  58.  
  59.  
  60. {Summary of IBM-PC Asynchronous Adapter Registers.  From:
  61.   Compute!'s Mapping the IBM PC and PCjr, by Russ Davis
  62.   (Greensboro, North Carolina, 1985: COMPUTE! Publications, Inc.),
  63.   pp. 290-292.
  64.  
  65. Addresses given are for COM1 and COM2, respectively.  The names given
  66. in parentheses are the names used in this module.
  67.  
  68.  
  69. 3F8/2F8 (uart_data) Read: transmit buffer.  Write: receive buffer, or baud
  70. rate divisor LSB if port 3FB, bit 7 = 1.
  71.  
  72. 3F9/2F9 (uart_ier) Write: Interrupt enable register or baud rate divisor
  73. MSB if port 3FB, bit 7 = 1.
  74. PCjr baud rate divisor is different from other models;
  75. clock input is 1.7895 megahertz rather than 1.8432 megahertz.
  76. Interrupt enable register:
  77.     bits 7-4  forced to 0
  78.     bit 3     1=enable change-in-modem-status interrupt
  79.     bit 2     1=enable line-status interrupt
  80.     bit 1     1=enable transmit-register-empty interrupt
  81.     bit 0     1=data-available interrupt
  82.  
  83. 3FA/2FA (uart_iir) Interrupt identification register (prioritized)
  84.      bits 7-3  forced to 0
  85.      bits 2-1  00=change-in-modem-status (lowest)
  86.      bits 2-1  01=transmit-register-empty (low)
  87.      bits 2-1  10=data-available (high)
  88.      bits 2-1  11=line status (highest)
  89.      bit 0     1=no interrupt pending
  90.      bit 0     0=interrupt pending
  91.  
  92. 3FB/2FB (uart_lcr) Line control register
  93.      bit 7  0=normal, 1=address baud rate divisor registers
  94.      bit 6  0=break disabled, 1=enabled
  95.      bit 5  0=don't force parity
  96.             1=if bit 4-3=01 parity always 1
  97.               if bit 4-3=11 parity always 0
  98.               if bit 3=0 no parity
  99.      bit 4  0=odd parity,1=even
  100.      bit 3  0=no parity,1=parity
  101.      bit 2  0=1 stop bit
  102.             1=1.5 stop bits if 5 bits/character or
  103.               2 stop bits if 6-8 bits/character
  104.      bits 1-0  00=5 bits/character
  105.                01=6 bits/character
  106.                10=7 bits/character
  107.                11=8 bits/character
  108.  
  109.      bits 5..3: 000 No parity
  110.                 001 Odd parity
  111.                 010 No parity
  112.                 011 Even parity
  113.                 100 No parity
  114.                 101 Parity always 1
  115.                 110 No parity
  116.                 111 Parity always 0
  117.  
  118.  
  119. 3FC/2FC (uart_mcr) Modem control register
  120.      bits 7-5  forced to zero
  121.      bit 4     0=normal, 1=loop back test
  122.      bits 3-2  all PCs except PCjr
  123.      bit 3     1=interrupts to system bus, user-designated output: OUT2
  124.      bit 2     user-designated output, OUT1
  125.      bit 1     1=activate rts
  126.      bit 0     1=activate dtr
  127.  
  128. 3FD/2FD (uart_lsr) Line status register
  129.      bit 7  forced to 0
  130.      bit 6  1=transmit shift register is empty
  131.      bit 5  1=transmit hold register is empty
  132.      bit 4  1=break received
  133.      bit 3  1=framing error received
  134.      bit 2  1=parity error received
  135.      bit 1  1=overrun error received
  136.      bit 0  1=data received
  137.  
  138. 3FE/2FE (uart_msr) Modem status register
  139.      bit 7  1=receive line signal detect
  140.      bit 6  1=ring indicator (all PCs except PCjr)
  141.      bit 5  1=dsr
  142.      bit 4  1=cts
  143.      bit 3  1=receive line signal detect has changed state
  144.      bit 2  1=ring indicator has changed state (all PCs except PCjr)
  145.      bit 1  1=dsr has changed state
  146.      bit 0  1=cts has changed state
  147.  
  148. 3FF/2FF (uart_spr) Scratch pad register.}
  149.  
  150.  
  151. {Maximum port number (minimum is 1) }
  152.  
  153. CONST
  154.   max_port = 4;
  155.  
  156.  
  157. {Base i/o address for each COM port}
  158.  
  159. CONST
  160.   uart_base: ARRAY [1..max_port] OF Integer = ($3F8, $2F8, $3E8, $2E8);
  161.  
  162.  
  163. {Interrupt numbers for each COM port}
  164.  
  165. CONST
  166.   intnums: ARRAY [1..max_port] OF Byte = ($0C, $0B, $0C, $0B);
  167.  
  168.  
  169. {i8259 interrupt levels for each port}
  170.  
  171. CONST
  172.   i8259levels: ARRAY [1..max_port] OF Byte = (4, 3, 4, 3);
  173.  
  174.  
  175. {This variable is TRUE if the interrupt driver has been installed, or FALSE
  176. if it hasn't.  It's used to prevent installing twice or deinstalling when not
  177. installed.}
  178.  
  179.  
  180.  
  181. {UART i/o addresses.  Values depend upon which COMM port is selected.}
  182.  
  183. VAR
  184.   uart_data: Word;             {Data register}
  185.   uart_ier : Word;             {Interrupt enable register}
  186.   uart_iir : Word;             {Interrupt identification register}
  187.   uart_lcr : Word;             {Line control register}
  188.   uart_mcr : Word;             {Modem control register}
  189.   uart_lsr : Word;             {Line status register}
  190.   uart_msr : Word;             {Modem status register}
  191.   uart_spr : Word;             {Scratch pad register}
  192.  
  193.  
  194. {Original contents of IER and MCR registers.  Used to restore UART
  195. to whatever state it was in before this driver was loaded.}
  196.  
  197. VAR
  198.   old_ier: Byte;
  199.   old_mcr: Byte;
  200.  
  201.  
  202. {Original contents of interrupt vector.  Used to restore the vector when
  203. the interrupt driver is deinstalled.}
  204.  
  205. VAR
  206.   old_vector: Pointer;
  207.  
  208.  
  209. {Original contents of interrupt controller mask.  Used to restore the
  210. bit pertaining to the comm controller we're using.}
  211.  
  212. VAR
  213.   old_i8259_mask: Byte;
  214.  
  215.  
  216. {Bit mask for i8259 interrupt controller}
  217.  
  218. VAR
  219.   i8259bit: Byte;
  220.  
  221.  
  222. {Interrupt vector number}
  223.  
  224. VAR
  225.   intnum: Byte;
  226.  
  227.  
  228. {Receive queue.  Received characters are held here until retrieved by
  229. ComRx.}
  230.  
  231. CONST
  232.   rx_queue_size = 128;   {Change to suit}
  233. VAR
  234.   rx_queue: ARRAY [1..rx_queue_size] OF Byte;
  235.   rx_in   : Word;        {Index of where to store next character}
  236.   rx_out  : Word;        {Index of where to retrieve next character}
  237.   rx_chars: Word;        {Number of chars in queue}
  238.  
  239.  
  240. {Transmit queue.  Characters to be transmitted are held here until the
  241. UART is ready to transmit them.}
  242.  
  243. CONST
  244.   tx_queue_size = 16;    {Change to suit}
  245. VAR
  246.   tx_queue: ARRAY [1..tx_queue_size] OF Byte;
  247.   tx_in   : Integer;     {Index of where to store next character}
  248.   tx_out  : Integer;     {Index of where to retrieve next character}
  249.   tx_chars: integer;     {Number of chars in queue}
  250.  
  251.  
  252. {This variable is used to save the next link in the "exit procedure" chain.}
  253.  
  254. VAR
  255.   exit_save: Pointer;
  256.  
  257. PROCEDURE disable_interrupts;
  258. INLINE
  259.   (
  260.   $FA    {CLI}
  261.   );
  262.  
  263. PROCEDURE enable_interrupts;
  264. INLINE
  265.   (
  266.   $FB    {STI}
  267.   );
  268.  
  269.  
  270.  
  271.  
  272. {Interrupt driver.  The UART is programmed to cause an interrupt whenever
  273. a character has been received or when the UART is ready to transmit another
  274. character.}
  275.  
  276. {$R-,S-}
  277. PROCEDURE Cominterrupt_driver; INTERRUPT;
  278.  
  279. VAR
  280.   ch   : Char;
  281.   iir  : Byte;
  282.   dummy: Byte;
  283.  
  284. BEGIN
  285.  
  286.   {While bit 0 of the interrupt identification register is 0, there is an
  287.   interrupt to process}
  288.  
  289.   iir := Port [uart_iir];
  290.  
  291.   WHILE NOT Odd (iir) DO
  292.     BEGIN
  293.  
  294.     CASE iir SHR 1 OF
  295.  
  296.       {iir = 100b: Received data available.  Get the character, and if
  297.       the buffer isn't full, then save it.  If the buffer is full,
  298.       then ignore it.}
  299.  
  300.       2:
  301.         BEGIN
  302.         ch := Char (Port [uart_data] );
  303.         IF (rx_chars <= rx_queue_size) THEN
  304.           BEGIN
  305.           rx_queue [rx_in] := Ord (ch);
  306.           Inc (rx_in);
  307.           IF rx_in > rx_queue_size THEN
  308.             rx_in := 1;
  309.           rx_chars := Succ (rx_chars);
  310.           END;
  311.         END;
  312.  
  313.       {iir = 010b: Transmit register empty.  If the transmit buffer
  314.       is empty, then disable the transmitter to prevent any more
  315.       transmit interrupts.  Otherwise, send the character.
  316.  
  317.       The test of the line-status-register is to see if the transmit
  318.       holding register is truly empty.  Some UARTS seem to cause transmit
  319.       interrupts when the holding register isn't empty, causing transmitted
  320.       characters to be lost.}
  321.  
  322.       1:
  323.         IF (tx_chars <= 0) THEN
  324.           Port [uart_ier] := Port [uart_ier] AND NOT 2
  325.         ELSE
  326.           IF Odd (Port [uart_lsr] SHR 5) THEN
  327.             BEGIN
  328.             Port [uart_data] := tx_queue [tx_out];
  329.             Inc (tx_out);
  330.             IF tx_out > tx_queue_size THEN
  331.               tx_out := 1;
  332.             Dec (tx_chars);
  333.             END;
  334.  
  335.       {iir = 001b: Change in modem status.  We don't expect this interrupt,
  336.       but if one ever occurs we need to read the line status to reset it
  337.       and prevent an endless loop.}
  338.  
  339.       0:
  340.         dummy := Port [uart_msr];
  341.  
  342.       {iir = 111b: Change in line status.  We don't expect this interrupt,
  343.       but if one ever occurs we need to read the line status to reset it
  344.       and prevent an endless loop.}
  345.  
  346.       3:
  347.         dummy := Port [uart_lsr];
  348.  
  349.       END;
  350.  
  351.     iir := Port [uart_iir];
  352.     END;
  353.  
  354.   {Tell the interrupt controller that we're done with this interrupt}
  355.  
  356.   Port [$20] := $20;
  357.  
  358. END;
  359. {$R+,S+}
  360.  
  361.  
  362. {Flush (empty) the receive buffer.}
  363.  
  364. PROCEDURE ComFlushRx;
  365. BEGIN
  366.   disable_interrupts;
  367.   rx_chars := 0;
  368.   rx_in    := 1;
  369.   rx_out   := 1;
  370.   enable_interrupts;
  371. END;
  372.  
  373.  
  374. {Flush (empty) transmit buffer.}
  375.  
  376. PROCEDURE ComFlushTx;
  377. BEGIN
  378.   disable_interrupts;
  379.   tx_chars := 0;
  380.   tx_in    := 1;
  381.   tx_out   := 1;
  382.   enable_interrupts;
  383. END;
  384.  
  385.  
  386. {This function returns TRUE if a carrier is present.}
  387.  
  388. FUNCTION Comcarrier: Boolean;
  389. BEGIN
  390.   Comcarrier := ComInstalled AND Odd (Port [uart_msr] SHR 7);
  391. END;
  392.  
  393.  
  394. {Get a character from the receive buffer.  If the buffer is empty, return
  395. a NULL (#0).}
  396.  
  397. FUNCTION ComRx: Char;
  398. BEGIN
  399.   IF NOT ComInstalled OR (rx_chars = 0) THEN
  400.     ComRx := #0
  401.   ELSE
  402.     BEGIN
  403.     disable_interrupts;
  404.     ComRx := Chr (rx_queue [rx_out] );
  405.     Inc (rx_out);
  406.     IF rx_out > rx_queue_size THEN
  407.       rx_out := 1;
  408.     Dec (rx_chars);
  409.     enable_interrupts;
  410.     END;
  411. END;
  412.  
  413.  
  414. {This function returns True if ComTx can accept a character.}
  415.  
  416. FUNCTION ComTxReady: Boolean;
  417. BEGIN
  418.   ComTxReady := (tx_chars < tx_queue_size) OR NOT ComInstalled;
  419. END;
  420.  
  421.  
  422. {This function returns True if the transmit buffer is empty.}
  423.  
  424. FUNCTION ComTxEmpty: Boolean;
  425. BEGIN
  426.   ComTxEmpty := (tx_chars = 0) OR NOT ComInstalled;
  427. END;
  428.  
  429.  
  430. {This function returns True if the receive buffer is empty.}
  431.  
  432. FUNCTION ComRxEmpty: Boolean;
  433. BEGIN
  434.   ComRxEmpty := (rx_chars = 0) OR NOT ComInstalled;
  435. END;
  436.  
  437.  
  438. {Send a character.  Waits until the transmit buffer isn't full, then puts
  439. the character into it.  The interrupt driver will send the character
  440. once the character is at the head of the transmit queue and a transmit
  441. interrupt occurs.}
  442.  
  443. PROCEDURE ComTx (ch: Char);
  444. BEGIN
  445.   IF ComInstalled THEN
  446.     BEGIN
  447.     REPEAT UNTIL ComTxReady;
  448.     disable_interrupts;
  449.     tx_queue [tx_in] := Ord (ch);
  450.     IF tx_in < tx_queue_size THEN
  451.       Inc (tx_in)
  452.     ELSE
  453.       tx_in := 1;
  454.     Inc (tx_chars);
  455.     Port [uart_ier] := Port [uart_ier] OR 2;
  456.     enable_interrupts;
  457.     END;
  458. END;
  459.  
  460.  
  461. {Send a whole string}
  462.  
  463. PROCEDURE ComRxString (st: String);
  464. VAR
  465.   i: Byte;
  466. BEGIN
  467.   FOR i := 1 TO Length (st) DO
  468.     ComTx (st [i] );
  469. END;
  470.  
  471.  
  472. {Lower (deactivate) the DTR line.  Causes most modems to hang up.}
  473.  
  474. PROCEDURE ComLowerDTR;
  475. BEGIN
  476.   IF ComInstalled THEN
  477.     BEGIN
  478.     disable_interrupts;
  479.     Port [uart_mcr] := Port [uart_mcr] AND NOT 1;
  480.     enable_interrupts;
  481.     END;
  482. END;
  483.  
  484.  
  485. {Raise (activate) the DTR line.}
  486.  
  487. PROCEDURE ComRaiseDTR;
  488. BEGIN
  489.   IF ComInstalled THEN
  490.     BEGIN
  491.     disable_interrupts;
  492.     Port [uart_mcr] := Port [uart_mcr] OR 1;
  493.     enable_interrupts;
  494.     END;
  495. END;
  496.  
  497.  
  498. {Set the baud rate.  Accepts any speed between 2 and 65535.  However,
  499. I am not sure that extremely high speeds (those above 19200) will
  500. always work, since the baud rate divisor will be six or less, where a
  501. difference of one can represent a difference in baud rate of
  502. 3840 bits per second or more.}
  503.  
  504. PROCEDURE ComSetSpeed (speed: Word);
  505. VAR
  506.   divisor: Word;
  507. BEGIN
  508.   IF ComInstalled THEN
  509.     BEGIN
  510.     IF speed < 2 THEN speed := 2;
  511.     divisor := 115200 DIV speed;
  512.     disable_interrupts;
  513.     Port  [uart_lcr]  := Port [uart_lcr] OR $80;
  514.     Portw [uart_data] := divisor;
  515.     Port  [uart_lcr]  := Port [uart_lcr] AND NOT $80;
  516.     enable_interrupts;
  517.     END;
  518. END;
  519.  
  520.  
  521. {Set the parity and stop bits as follows:
  522.  
  523.   Comnone    8 data bits, no parity
  524.   Comeven    7 data bits, even parity
  525.   Comodd     7 data bits, odd parity
  526.   Comzero    7 data bits, parity always zero
  527.   Comone     7 data bits, parity always one}
  528.  
  529. PROCEDURE ComSetParity (parity: Comparity; stop_bits: Byte);
  530. VAR
  531.   lcr: Byte;
  532. BEGIN
  533.   CASE parity OF
  534.     Comnone: lcr := $00 OR $03;
  535.     Comeven: lcr := $18 OR $02;
  536.     Comodd : lcr := $08 OR $02;
  537.     Comzero: lcr := $38 OR $02;
  538.     Comone : lcr := $28 OR $02;
  539.     END;
  540.   IF stop_bits = 2 THEN
  541.     lcr := lcr OR $04;
  542.   disable_interrupts;
  543.   Port [uart_lcr] := Port [uart_lcr] AND $40 OR lcr;
  544.   enable_interrupts;
  545. END;
  546.  
  547. {Install the communications driver.  Portnum should be 1..max_port.
  548. Error codes returned are:
  549.  
  550.   0 - No error
  551.   1 - Invalid port number
  552.   2 - UART for that port is not present
  553.   3 - Already installed, new installation ignored}
  554.  
  555. PROCEDURE ComInstall
  556.   (
  557.   portnum  : Word;
  558.   VAR error: Word
  559.   );
  560. VAR
  561.   ier: Byte;
  562. BEGIN
  563.   IF ComInstalled THEN
  564.     error := 3
  565.   ELSE
  566.     IF (portnum < 1) OR (portnum > max_port) THEN
  567.       error := 1
  568.     ELSE
  569.       BEGIN
  570.  
  571.       {Set i/o addresses and other hardware specifics for selected port}
  572.  
  573.       uart_data := uart_base [portnum];
  574.       uart_ier  := uart_data + 1;
  575.       uart_iir  := uart_data + 2;
  576.       uart_lcr  := uart_data + 3;
  577.       uart_mcr  := uart_data + 4;
  578.       uart_lsr  := uart_data + 5;
  579.       uart_msr  := uart_data + 6;
  580.       uart_spr  := uart_data + 7;
  581.       intnum    := intnums [portnum];
  582.       i8259bit  := 1 SHL i8259levels [portnum];
  583.  
  584.       {Return error if hardware not installed}
  585.  
  586.       old_ier := Port [uart_ier];
  587.       Port [uart_ier] := 0;
  588.       IF Port [uart_ier] <> 0 THEN
  589.         error := 2
  590.       ELSE
  591.         BEGIN
  592.         error := 0;
  593.  
  594.         {Save original interrupt controller mask, then disable the
  595.         interrupt controller for this interrupt.}
  596.  
  597.         disable_interrupts;
  598.         old_i8259_mask := Port [$21];
  599.         Port [$21] := old_i8259_mask OR i8259bit;
  600.         enable_interrupts;
  601.  
  602.         {Clear the transmit and receive queues}
  603.  
  604.         ComFlushTx;
  605.         ComFlushRx;
  606.  
  607.         {Save current interrupt vector, then set the interrupt vector to
  608.         the address of our interrupt driver.}
  609.  
  610.         GetIntVec (intnum, old_vector);
  611.         SetIntVec (intnum, @Cominterrupt_driver);
  612.         ComInstalled := True;
  613.  
  614.         {Set parity to none, turn off BREAK signal, and make sure
  615.         we're not addressing the baud rate registers.}
  616.  
  617.         Port [uart_lcr] := 3;
  618.  
  619.         {Save original contents of modem control register, then enable
  620.         interrupts to system bus and activate RTS.  Leave DTR the way
  621.         it was.}
  622.  
  623.         disable_interrupts;
  624.         old_mcr := Port [uart_mcr];
  625.         Port [uart_mcr] := $A OR (old_mcr AND 1);
  626.         enable_interrupts;
  627.  
  628.         {Enable interrupt on data-available.  The interrupt for
  629.         transmit-ready is enabled when a character is put into the
  630.         transmit queue, and disabled when the transmit queue is empty.}
  631.  
  632.         Port [uart_ier] := 1;
  633.  
  634.         {Enable the interrupt controller for this interrupt.}
  635.  
  636.         disable_interrupts;
  637.         Port [$21] := Port [$21] AND NOT i8259bit;
  638.         enable_interrupts;
  639.  
  640.         END;
  641.       END;
  642. END;
  643.  
  644.  
  645. {Deinstall the interrupt driver completely.  It doesn't change the baud rate
  646. or mess with DTR; it tries to leave the interrupt vectors and enables and
  647. everything else as it was when the driver was installed.
  648.  
  649. This procedure MUST be called by the exit procedure of this module before
  650. the program exits to DOS, or the interrupt driver will still
  651. be attached to its vector -- the next communications interrupt that came
  652. along would jump to the interrupt driver which is no longer protected and
  653. may have been written over.}
  654.  
  655.  
  656. PROCEDURE Comdeinstall;
  657. BEGIN
  658.   IF ComInstalled THEN
  659.     BEGIN
  660.  
  661.     ComInstalled := False;
  662.  
  663.     {Restore Modem-Control-Register and Interrupt-Enable-Register.}
  664.  
  665.     Port [uart_mcr] := old_mcr;
  666.     Port [uart_ier] := old_ier;
  667.  
  668.     {Restore appropriate bit of interrupt controller's mask}
  669.  
  670.     disable_interrupts;
  671.     Port [$21] := Port [$21] AND NOT i8259bit OR
  672.      old_i8259_mask AND i8259bit;
  673.     enable_interrupts;
  674.  
  675.     {Reset the interrupt vector}
  676.  
  677.     SetIntVec (intnum, old_vector);
  678.  
  679.     END;
  680. END;
  681.  
  682.  
  683. {This procedure is called when the program exits for any reason.  It
  684. deinstalls the interrupt driver.}
  685.  
  686. {$F+} PROCEDURE exit_procedure; {$F-}
  687. BEGIN
  688.   Comdeinstall;
  689.   ExitProc := exit_save;
  690. END;
  691.  
  692.  
  693. {This installs the exit procedure.}
  694.  
  695. BEGIN
  696.   exit_save:=ExitProc;
  697.   ExitProc:=@exit_procedure;
  698.   if paramstr(1)='/(C)' then begin
  699.     writeln('LSCOMM.PAS  V1.00  LightSpeed(TM) Comm Driver for Modem I/O');
  700.     writeln('            Copyright (C) 1993 by Onkel Dittmeyer of SLAM !');
  701.     writeln;
  702.     readln;
  703.   end;
  704. END.
  705.