1175 lines
40 KiB
C
1175 lines
40 KiB
C
/* -------------------------------------------------------------------------
|
|
saturn - A poor-man's emulator of some HP calculators
|
|
Copyright (C) 1998-2000 Ivan Cibrario Bertolotti
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with the documentation of this program; if not, write to
|
|
the Free Software Foundation, Inc.,
|
|
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
For more information, please contact the author, preferably by email,
|
|
at the following address:
|
|
|
|
Ivan Cibrario Bertolotti
|
|
IRITI - National Research Council
|
|
c/o IEN "Galileo Ferraris"
|
|
Strada delle Cacce, 91
|
|
10135 - Torino (ITALY)
|
|
|
|
email: cibrario@iriti.cnr.it
|
|
------------------------------------------------------------------------- */
|
|
|
|
/* +-+ */
|
|
|
|
/* .+
|
|
|
|
.identifier : $Id: serial.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $
|
|
.context : SATURN, Saturn CPU / HP48 emulator
|
|
.title : $RCSfile: serial.c,v $
|
|
.kind : C source
|
|
.author : Ivan Cibrario B.
|
|
.site : CSTV-CNR
|
|
.creation : 13-Sep-2000
|
|
.keywords : *
|
|
.description :
|
|
|
|
SASM.DOC by HP (HORN disk 4)
|
|
Guide to the Saturn Processor Rev. 0.00f and 1.0b by Matthew Mastracci
|
|
entries.srt by Mika Heiskanen (mheiskan@vipunen.hut.fi)
|
|
x48 source code by Eddie C. Dost (ecd@dressler.de)
|
|
Emu48 source code by Sebastien Cariler
|
|
|
|
.include : config.h, machdep.h, serial.h
|
|
|
|
.notes :
|
|
$Log: serial.c,v $
|
|
Revision 4.1 2000/12/11 09:54:19 cibrario
|
|
Public release.
|
|
|
|
Revision 3.17 2000/11/23 17:01:45 cibrario
|
|
Implemented sutil library and assorted bug fixes:
|
|
- Fixed UpdateTCS macro; it gave wrong results when trb was full
|
|
- in SerialInit(), ensure that the pty is fully transparent by default
|
|
- in SerialInit(), slave pty must have O_NONBLOCK set, otherwise
|
|
SerialClose() could hang (rare)
|
|
- in HandleSerial(), transmit ring buffer must be emptied even
|
|
when !IOC_SON
|
|
|
|
Revision 3.16 2000/11/21 16:41:08 cibrario
|
|
Ultrix/IRIX support:
|
|
- Added sgi/IRIX support (USE_STREAMSPTY)
|
|
- Added fallback, dummy pty implementation
|
|
|
|
Revision 3.10 2000/10/24 16:14:58 cibrario
|
|
Added/Replaced GPL header
|
|
|
|
Revision 3.5 2000/10/02 09:51:21 cibrario
|
|
Linux support:
|
|
- libc6 >= 2.0 has openpty()
|
|
|
|
Revision 3.2 2000/09/22 14:34:55 cibrario
|
|
Implemented preliminary support of HP49 hw architecture:
|
|
- Conditionally (#ifdef HP49_SUPPORT) simplified handling of
|
|
RCS_RBZ and RCS_RBF bits of RCS register.
|
|
- Conditionally (#ifdef HP49_SUPPORT) disabled local ECHO on master
|
|
pty when USE_OPENPTY is in effect; this avoid spurious rx
|
|
when no process is connected to the slave pty yet. Apparently,
|
|
USE_STREAMSPTY does not suffer from this.
|
|
- Conditionally (#ifdef HP49_SUPPORT) removed warning message
|
|
when reading from an empty RRB.
|
|
|
|
* Revision 2.6 2000/09/15 09:23:02 cibrario
|
|
* - Implemented USE_STREAMSPTY (needed to build saturn on Solaris)
|
|
* - Avoided name clash on ADDRESS_MASK when including pty headers
|
|
* (Digital UNIX platform)
|
|
* - Enhanced documentation of public functions
|
|
*
|
|
* Revision 2.5 2000/09/14 15:44:08 cibrario
|
|
* *** empty log message ***
|
|
*
|
|
|
|
.- */
|
|
|
|
#ifndef lint
|
|
static char rcs_id[] = "$Id: serial.c,v 4.1 2000/12/11 09:54:19 cibrario Rel $";
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <setjmp.h>
|
|
#include <errno.h>
|
|
|
|
#include "config.h"
|
|
#include "machdep.h"
|
|
#include "cpu.h"
|
|
#include "serial.h" /* 2.5: Serial port emulation module */
|
|
#include "debug.h"
|
|
|
|
#define CHF_MODULE_ID SERIAL_CHF_MODULE_ID
|
|
#include "libChf/src/Chf.h"
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Determine pty implementation
|
|
|
|
Currently, the following two implementations are supported:
|
|
|
|
- openpty(); available on Digital UNIX and Linux
|
|
- STREAMS pseudo-tty driver (/dev/ptmx); available on Solaris 2.6
|
|
|
|
SERIAL_FORCE_OPENPTY and SERIAL_FORCE_STREAMSPTY can be defined in config.h
|
|
to force this module to use a particular implementation; otherwise,
|
|
a (hopefully) appropriate implementation will be automatically selected
|
|
by the cpp code that follows, depending on the build platform.
|
|
|
|
At the end of the cpp code below, exactly one of the macros
|
|
USE_OPENPTY and USE_STREAMSPTY will be defined; they should be used
|
|
to conditionally select the implementation-dependent portions of code.
|
|
|
|
---------------------------------------------------------------------------*/
|
|
|
|
#ifdef SERIAL_FORCE_OPENPTY
|
|
# define USE_OPENPTY
|
|
# define FORCED
|
|
#endif
|
|
|
|
#ifdef SERIAL_FORCE_STREAMSPTY
|
|
# define USE_STREAMSPTY
|
|
# define FORCED
|
|
#endif
|
|
|
|
/* 3.5: Added linux support (__linux__ cpp macro) */
|
|
#if ( defined( __osf__ ) || defined( __linux__ ) ) && !defined( FORCED )
|
|
# define USE_OPENPTY
|
|
#endif
|
|
|
|
/* 3.16: Added sgi support (__sgi cpp macro) */
|
|
#if ( defined( __sun__ ) || defined( __sgi ) ) && !defined( FORCED )
|
|
# define USE_STREAMSPTY
|
|
#endif
|
|
|
|
/* 3.16: If no appropriate pty implementation has been found,
|
|
throw a dummy implementation in.
|
|
*/
|
|
#if !defined( USE_OPENPTY ) && !defined( USE_STREAMSPTY )
|
|
# define USE_NOPTY
|
|
#endif
|
|
|
|
#undef FORCED
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Include pty implementation-specific headers and definitions
|
|
---------------------------------------------------------------------------*/
|
|
|
|
#ifdef USE_OPENPTY
|
|
# undef ADDRESS_MASK /* 2.6: Avoid name clash 8-( */
|
|
# include <fcntl.h> /* fcntl() */
|
|
# include <unistd.h> /* ttyname() */
|
|
# include <pty.h> /* openpty() */
|
|
# include <termios.h> /* tcgetattr()/tcsetattr() */
|
|
# include <sys/termios.h>
|
|
# include <sys/ioctl.h>
|
|
#endif
|
|
|
|
#ifdef USE_STREAMSPTY
|
|
# undef ADDRESS_MASK /* 2.6: Avoid name clash 8-( */
|
|
/* stdlib.h already included */
|
|
# include <fcntl.h> /* open(), fcntl() */
|
|
# include <unistd.h>
|
|
# include <termios.h> /* tcgetattr()/tcsetattr() */
|
|
# include <stropts.h> /* ioctl() */
|
|
# define PTY_MASTER "/dev/ptmx" /* Master cloning device */
|
|
#endif
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Private implementation of ring buffers
|
|
|
|
The following data type definitions, macros, and functions together
|
|
implement the ring buffers used to hold serial data being transmitted
|
|
and received. Multibyte pushes and pops are efficiently supported:
|
|
the whole ring buffer can be filled/emptied completely with only
|
|
two, non-overlapping memcpy(), read() or write() operations.
|
|
|
|
---------------------------------------------------------------------------*/
|
|
|
|
#define RB_SIZE 1024 /* Buffer size (# of characters) */
|
|
|
|
/* Hello, I am a RingBuffer... 8-} */
|
|
struct RingBuffer {
|
|
int n; /* Number of full slots: 0 <= n <= RB_SIZE */
|
|
int8 *rp, *wp; /* Read/Write pointers */
|
|
int8* ep; /* Pointer to the end of .data[] */
|
|
int8 data[ RB_SIZE ]; /* Buffer storage */
|
|
};
|
|
|
|
/* Basic macros:
|
|
|
|
Min(a, b) returns the minimum (as told by '<') between a and b;
|
|
warning: evaluates a and b twice.
|
|
|
|
ReadPointer(rb) returns the read pointer of a given buffer; it
|
|
points to ContFullSlots() full buffer slots.
|
|
|
|
FullSlots(rb) returns the number of full slots of a given buffer;
|
|
the slots are not necessarily contiguous.
|
|
|
|
ContFullSlots(rb) returns the number of *contiguous* full slots of a
|
|
given buffer, starting at the current ReadPointer;
|
|
this macro is guaranteed to return a strictly
|
|
positive value if the buffer is not empty.
|
|
|
|
EmptySlots(rb) returns the number of empty slots of a given buffer;
|
|
the slots are not necessarily contiguous.
|
|
|
|
ContEmptySlots(rb) returns the number of *contiguous* empty slots of a
|
|
given buffer, starting at the current WritePointer;
|
|
this macro is guaranteed to return a strictly
|
|
positive value if the buffer is not full.
|
|
|
|
UpdateReadPointer(rb, n)
|
|
moves the read pointer of ring buffer rb n slots
|
|
forward
|
|
|
|
UpdateWritePointer(rb, n)
|
|
moves the write pointer of ring buffer rb n slots
|
|
forward
|
|
|
|
Push(rb, c) pushes character c into ring buffer rb; this macro
|
|
must be invoked only if EmptySlots(rb) is strictly
|
|
positive.
|
|
|
|
Pull(rb, cp) pulls a character from ring buffer rb and stores it
|
|
into *cp; this macro must be invoked only if
|
|
EmptySlots(rb) is strictly positive.
|
|
|
|
InitRingBuffer(rb) initializes ring buffer rb; it must be called
|
|
before using the ring buffer in any way.
|
|
*/
|
|
#define Min( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) )
|
|
#define ReadPointer( rb ) ( ( rb ).rp )
|
|
#define FullSlots( rb ) ( ( rb ).n )
|
|
#define ContFullSlots( rb ) ( Min( FullSlots( rb ), ( rb ).ep - ReadPointer( rb ) ) )
|
|
#define WritePointer( rb ) ( ( rb ).wp )
|
|
#define EmptySlots( rb ) ( RB_SIZE - FullSlots( rb ) )
|
|
#define ContEmptySlots( rb ) ( Min( EmptySlots( rb ), ( rb ).ep - WritePointer( rb ) ) )
|
|
|
|
#define UpdateReadPointer( rb, n ) \
|
|
{ \
|
|
FullSlots( rb ) -= ( n ); \
|
|
ReadPointer( rb ) += ( n ); \
|
|
if ( ReadPointer( rb ) >= ( rb ).ep ) \
|
|
ReadPointer( rb ) -= RB_SIZE; \
|
|
}
|
|
|
|
#define UpdateWritePointer( rb, n ) \
|
|
{ \
|
|
FullSlots( rb ) += ( n ); \
|
|
WritePointer( rb ) += ( n ); \
|
|
if ( WritePointer( rb ) >= ( rb ).ep ) \
|
|
WritePointer( rb ) -= RB_SIZE; \
|
|
}
|
|
|
|
#define Push( rb, c ) \
|
|
{ \
|
|
FullSlots( rb )++; \
|
|
*( WritePointer( rb )++ ) = c; \
|
|
if ( WritePointer( rb ) >= ( rb ).ep ) \
|
|
WritePointer( rb ) -= RB_SIZE; \
|
|
}
|
|
|
|
#define Pull( rb, cp ) \
|
|
{ \
|
|
FullSlots( rb )--; \
|
|
*cp = *( ReadPointer( rb )++ ); \
|
|
if ( ReadPointer( rb ) >= ( rb ).ep ) \
|
|
ReadPointer( rb ) -= RB_SIZE; \
|
|
}
|
|
|
|
#define InitRingBuffer( rb ) \
|
|
{ \
|
|
FullSlots( rb ) = 0; \
|
|
ReadPointer( rb ) = WritePointer( rb ) = ( rb ).data; \
|
|
( rb ).ep = ( rb ).data + RB_SIZE; \
|
|
}
|
|
|
|
/* Push/Pull functions:
|
|
|
|
PullAndWrite(rbp, fd)
|
|
This function pulls as many characters as possible from the ring buffer
|
|
pointed by rbp and write()s them into file descriptor fd. Returns the
|
|
number of characters actually written, or a negative integer if
|
|
write() reported an error.
|
|
|
|
ReadAndPush(rbp, fd)
|
|
This function reads as many characters as possible from file descriptor fd
|
|
and pushes them into the ring buffer pointed by rbp. Returns the
|
|
number of characters actually read, or a negative integer if
|
|
read() reported an error.
|
|
|
|
*/
|
|
static int PullAndWrite( struct RingBuffer* rbp, int fd )
|
|
{
|
|
int total = 0; /* Total # of chars written */
|
|
|
|
while ( FullSlots( *rbp ) > 0 ) {
|
|
int chunk = ContFullSlots( *rbp ); /* Chunk size */
|
|
int result; /* # of chars written */
|
|
|
|
/* write() takes its data from ReadPointer (full slots) */
|
|
result = write( fd, ReadPointer( *rbp ), chunk );
|
|
|
|
if ( result < 0 && errno != EAGAIN )
|
|
/* write() failed; return an error indication */
|
|
return -1;
|
|
|
|
if ( result > 0 ) {
|
|
/* write() wrote at least one character; update ReadPointer */
|
|
total += result;
|
|
UpdateReadPointer( *rbp, result );
|
|
}
|
|
|
|
if ( result != chunk )
|
|
/* Partial success of write(); break loop for now */
|
|
break;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
static int ReadAndPush( struct RingBuffer* rbp, int fd )
|
|
{
|
|
int total = 0; /* Total # of chars read */
|
|
|
|
while ( EmptySlots( *rbp ) > 0 ) {
|
|
int chunk = ContEmptySlots( *rbp ); /* Chunk size */
|
|
int result; /* # of chars read */
|
|
|
|
/* read() puts its data into WritePointer (empty slots) */
|
|
result = read( fd, WritePointer( *rbp ), chunk );
|
|
|
|
if ( result < 0 && errno != EAGAIN )
|
|
/* read() failed; return an error indication */
|
|
return -1;
|
|
|
|
if ( result > 0 ) {
|
|
/* read() read at least one character; update WritePointer */
|
|
total += result;
|
|
UpdateWritePointer( *rbp, result );
|
|
}
|
|
|
|
if ( result != chunk )
|
|
/* Partial success of read(); break loop */
|
|
break;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Static variables, holding the status of the emulated port
|
|
---------------------------------------------------------------------------*/
|
|
|
|
static Nibble ioc = 0; /* I/O and interrupt control register */
|
|
#define IOC_SON 0x08 /* Serial port enable */
|
|
#define IOC_ETBE 0x04 /* Enable IRQ on TX buffer empty */
|
|
#define IOC_ERBF 0x02 /* Enable IRQ on RX buffer full */
|
|
#define IOC_ERBZ 0x01 /* Enable IRQ on RX buzy (sic) */
|
|
|
|
static Nibble rcs = 0; /* RX control & status register */
|
|
#define RCS_UNUSED 0x08 /* Unused (?) */
|
|
#define RCS_RER 0x04 /* RX Error */
|
|
#define RCS_RBZ 0x02 /* RX Buzy */
|
|
#define RCS_RBF 0x01 /* RX Buffer full */
|
|
|
|
static Nibble tcs = 0; /* TX control & status register */
|
|
#define TCS_BRK 0x08 /* Unknown (?) */
|
|
#define TCS_LPB 0x04 /* Unknown (?) */
|
|
#define TCS_TBZ 0x02 /* TX Buzy */
|
|
#define TCS_TBF 0x01 /* TX Buffer full */
|
|
|
|
static struct RingBuffer rrb; /* RX ring buffer */
|
|
static struct RingBuffer trb; /* TX ring buffer */
|
|
|
|
static char* pty_name; /* Name of pty's slave side */
|
|
static int master_pty; /* File descriptor of pty's master side */
|
|
static int slave_pty; /* File descriptor of pty's slave side */
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Helper macros
|
|
|
|
CheckIRQ This macro checks the current status of ioc, rcs, and tcs,
|
|
and posts an interrupt request if appropriate. It should be
|
|
called by all functions that modify the above-mentioned
|
|
registers, after the modification has been made.
|
|
Interrupt requests are posted only when IOC_SON is set.
|
|
|
|
UpdateRCS This macro updates the rcs register according to the
|
|
current rrb FullSlots:
|
|
- if the receiver ring buffer holds more than 1 character,
|
|
RCS_RBZ and RCS_RBF are both set
|
|
- if the receiver ring buffer holds exactly 1 character,
|
|
RCS_RBZ is reset and RCS_RBF is set
|
|
- if the receiver ring buffer is empty,
|
|
RCS_RBZ and RCS_RBF are both reset
|
|
|
|
3.2: If HP49_SUPPORT is enabled, the RCS_RBZ bit is
|
|
always left clear and only RCS_RBF is updated;
|
|
this is simpler and works well on the 48, too.
|
|
|
|
UpdateTCS This macro updates the tcs register according to the
|
|
current trb EmptySlots:
|
|
- if the transmitter ring buffer has more than 1 empty slot,
|
|
TCS_TBZ and TCS_TBF are both reset
|
|
- if the transmitter ring buffer has 1 empty slot,
|
|
TCS_TBZ is set and TCS_TBF is reset
|
|
- if the transmitter ring buffer is full,
|
|
TCS_TBZ and TCS_TBF are both set.
|
|
|
|
---------------------------------------------------------------------------*/
|
|
|
|
#define CheckIRQ \
|
|
if ( ( ioc & IOC_SON ) && ( ( ( ioc & IOC_ETBE ) && ( !( tcs & TCS_TBF ) ) ) || ( ( ioc & IOC_ERBF ) && ( rcs & RCS_RBF ) ) || \
|
|
( ( ioc & IOC_ERBZ ) && ( rcs & RCS_RBZ ) ) ) ) \
|
|
CpuIntRequest( INT_REQUEST_IRQ )
|
|
|
|
#ifdef HP49_SUPPORT
|
|
# define UpdateRCS \
|
|
if ( FullSlots( rrb ) > 0 ) { \
|
|
rcs |= RCS_RBF; \
|
|
} else { \
|
|
rcs &= ~( RCS_RBF ); \
|
|
}
|
|
#else
|
|
# define UpdateRCS \
|
|
if ( FullSlots( rrb ) > 1 ) { \
|
|
rcs |= ( RCS_RBF | RCS_RBZ ); \
|
|
} else if ( FullSlots( rrb ) > 0 ) { \
|
|
rcs |= RCS_RBF; \
|
|
rcs &= ~RCS_RBZ; \
|
|
} else { \
|
|
rcs &= ~( RCS_RBF | RCS_RBZ ); \
|
|
}
|
|
#endif
|
|
|
|
#define UpdateTCS \
|
|
if ( EmptySlots( trb ) > 1 ) { \
|
|
tcs &= ~( TCS_TBF | TCS_TBZ ); \
|
|
} else if ( EmptySlots( trb ) > 0 ) { \
|
|
tcs &= ~TCS_TBF; \
|
|
tcs |= TCS_TBZ; \
|
|
} else { \
|
|
tcs |= ( TCS_TBF | TCS_TBZ ); \
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------
|
|
Public functions
|
|
---------------------------------------------------------------------------*/
|
|
|
|
/* .+
|
|
|
|
.title : SerialInit
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function opens and initializes the pseudo-terminal that will
|
|
be used to emulate the calculator's serial port. When successful
|
|
it returns to the caller the symbolic name of the slave side of
|
|
the pseudo-terminal (that is, the side that will actually be used
|
|
by file transfer programs such as kermit), otherwise it signals
|
|
one or more status codes and returns NULL.
|
|
|
|
Notice that the string returned by SerialInit() shall not be
|
|
modified in any way.
|
|
|
|
.call :
|
|
slave_pty_name = SerialInit(void);
|
|
.input :
|
|
void
|
|
.output :
|
|
const char *slave_pty_name, name of slave pty or NULL
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_PTYNAME
|
|
SERIAL_W_NOPTY
|
|
SERIAL_F_OPENPTY
|
|
SERIAL_F_FCNTL
|
|
SERIAL_F_OPEN_MASTER
|
|
SERIAL_F_GRANTPT
|
|
SERIAL_F_UNLOCKPT
|
|
SERIAL_F_OPEN_SLAVE
|
|
SERIAL_F_PUSH
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
2.6, 15-Sep-2000, update
|
|
- implemented USE_STREAMSPTY
|
|
3.2, 22-Sep-2000, update
|
|
- conditionally (#ifdef HP49_SUPPORT) disabled local ECHO on master
|
|
pty when USE_OPENPTY is in effect; this avoid spurious rx
|
|
when no process is connected to the slave pty yet.
|
|
3.16, 16-Nov-2000, update
|
|
- added dummy pty implementation
|
|
3.17, 22-Nov-2000, bug fix
|
|
- ensure that the pty is fully transparent by default
|
|
- slave pty must have O_NONBLOCK set
|
|
.- */
|
|
const char* SerialInit( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "SerialInit" );
|
|
|
|
/* Initialize ring buffers */
|
|
InitRingBuffer( rrb );
|
|
InitRingBuffer( trb );
|
|
|
|
#ifndef USE_NOPTY
|
|
# ifdef USE_OPENPTY
|
|
/* Open pty master/slave pair; don't specify pty name, struct termios
|
|
and struct winsize.
|
|
*/
|
|
if ( openpty( &master_pty, &slave_pty, NULL, NULL, NULL ) ) {
|
|
pty_name = ( char* )NULL;
|
|
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_OPENPTY, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
} else {
|
|
int cur_flags;
|
|
|
|
pty_name = ttyname( slave_pty );
|
|
|
|
/* Remember: close the pty before exiting */
|
|
atexit( SerialClose );
|
|
|
|
/* Set O_NONBLOCK on master_pty */
|
|
if ( ( cur_flags = fcntl( master_pty, F_GETFL, 0 ) ) < 0 || fcntl( master_pty, F_SETFL, cur_flags | O_NONBLOCK ) < 0 ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_FCNTL, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
# endif
|
|
|
|
# ifdef USE_STREAMSPTY
|
|
/* Open master cloning device */
|
|
if ( ( master_pty = open( PTY_MASTER, O_RDWR | O_NONBLOCK ) ) < 0 ) {
|
|
pty_name = ( char* )NULL;
|
|
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_OPEN_MASTER, CHF_FATAL, PTY_MASTER ChfEnd;
|
|
ChfSignal();
|
|
} else {
|
|
/* Master side opened ok; change permissions and unlock slave side */
|
|
|
|
if ( grantpt( master_pty ) < 0 ) {
|
|
/* close() may modify errno; save it first */
|
|
ChfErrnoCondition;
|
|
|
|
( void )close( master_pty );
|
|
|
|
ChfCondition SERIAL_F_GRANTPT, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
if ( unlockpt( master_pty ) < 0 ) {
|
|
/* close() may modify errno; save it first */
|
|
ChfErrnoCondition;
|
|
|
|
( void )close( master_pty );
|
|
|
|
ChfCondition SERIAL_F_UNLOCKPT, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Get name of slave side; this must be done on the *master* side */
|
|
pty_name = ptsname( master_pty );
|
|
|
|
/* Open slave in nonblocking mode */
|
|
if ( ( slave_pty = open( pty_name, O_RDWR | O_NONBLOCK ) ) < 0 ) {
|
|
/* close() may modify errno; save it first */
|
|
ChfErrnoCondition;
|
|
|
|
( void )close( master_pty );
|
|
|
|
ChfCondition SERIAL_F_OPEN_SLAVE, CHF_FATAL, pty_name ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Remember: close the pty before exiting */
|
|
atexit( SerialClose );
|
|
|
|
/* Push appropriate STREAMS modules on the slave side to support
|
|
terminal emulation. This way, the slave side should be
|
|
indistinguishable from a real terminal.
|
|
*/
|
|
if ( ioctl( slave_pty, I_PUSH, "ptem" ) == -1 ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_PUSH, CHF_FATAL, "ptem" ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
if ( ioctl( slave_pty, I_PUSH, "ldterm" ) == -1 ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_PUSH, CHF_FATAL, "ldterm" ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
# endif
|
|
|
|
# ifdef HP49_SUPPORT
|
|
/* 3.17: Ensure that the pty is fully trasparent by default.
|
|
This allows to use most non-terminal-aware applications (such as od)
|
|
on the pty directly.
|
|
*/
|
|
{
|
|
struct termios tios;
|
|
|
|
if ( tcgetattr( slave_pty, &tios ) ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_TCGETATTR, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
tios.c_iflag &= ~( BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IUCLC | IXON | IXANY | IXOFF | IMAXBEL );
|
|
|
|
tios.c_iflag |= IGNBRK;
|
|
|
|
tios.c_oflag &=
|
|
~( OPOST | OLCUC | ONLCR | OCRNL | ONOCR | ONLRET | OFILL | OFDEL | NLDLY | CRDLY | TABDLY | BSDLY | VTDLY | FFDLY );
|
|
|
|
tios.c_cflag &= ~( CSIZE | CSTOPB | PARENB | PARODD );
|
|
|
|
tios.c_cflag |= CS8 | CREAD | HUPCL | CLOCAL;
|
|
|
|
tios.c_lflag &= ~( ISIG | ICANON | ECHO | ECHONL | IEXTEN );
|
|
|
|
/* read()s are satisfed when at least 1 character is available;
|
|
intercharacter/read timer disabled.
|
|
*/
|
|
tios.c_cc[ VMIN ] = 1;
|
|
tios.c_cc[ VTIME ] = 0;
|
|
|
|
if ( tcsetattr( slave_pty, TCSANOW, &tios ) ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_F_TCSETATTR, CHF_FATAL ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/* Publish pty name */
|
|
ChfCondition SERIAL_I_PTY_NAME, CHF_INFO, pty_name ChfEnd;
|
|
ChfSignal();
|
|
|
|
#else
|
|
/* Dummy implementation; do nothing */
|
|
pty_name = "";
|
|
|
|
ChfCondition SERIAL_W_NOPTY, CHF_WARNING ChfEnd;
|
|
ChfSignal();
|
|
#endif
|
|
|
|
return pty_name;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : SerialClose
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function closes the pseudo-terminal opened by SerialInit(), if
|
|
SerialInit() succeeded, it does nothing otherwise.
|
|
|
|
Notice that is is normally unnecessary to call this function directly,
|
|
because SerialInit() automatically registers it with atexit() upon
|
|
successful completion.
|
|
|
|
.call :
|
|
SerialClose();
|
|
.input :
|
|
void
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_E_PTY_CLOSE
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
2.6, 15-Sep-2000, update
|
|
- updated documentation
|
|
|
|
.- */
|
|
void SerialClose( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "SerialClose" );
|
|
|
|
if ( close( slave_pty ) || close( master_pty ) ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_E_PTY_CLOSE, CHF_ERROR ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : SerialPtyName
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function returns to the caller the name of the slave size of
|
|
the pseudo-terminal previously opened by SerialInit() if the
|
|
latter function succeeded, otherwise NULL.
|
|
|
|
Notice that the string returned by SerialPtyName() shall not be
|
|
modified in any way.
|
|
|
|
.call :
|
|
slave_pty_name = SerialPtyName();
|
|
.input :
|
|
void
|
|
.output :
|
|
const char *slave_pty_name, name of slave pty opened by
|
|
SerialInit()
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
const char* SerialPtyName( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "SerialPtyName" );
|
|
|
|
return pty_name;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_IOC_Read
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a read of the serial I/O and interrupt control
|
|
register and returns its current value to the caller.
|
|
|
|
Reading IOC returns the current value of the ioc emulation register
|
|
and does not trigger any ancillary action.
|
|
|
|
.call :
|
|
n = Serial_IOC_Read();
|
|
.input :
|
|
void
|
|
.output :
|
|
Nibble n, current value of emulated register
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_READ
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
Nibble Serial_IOC_Read( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_IOC_Read" );
|
|
debug2( DEBUG_C_SERIAL, SERIAL_I_READ, "IOC", ioc );
|
|
|
|
return ioc;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_RCS_Read
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a read of the serial receiver control & status
|
|
register and returns its current value to the caller.
|
|
|
|
Reading RCS returns the current value of the rcs emulation register
|
|
and does not trigger any ancillary action.
|
|
|
|
.call :
|
|
n = Serial_RCS_Read();
|
|
.input :
|
|
void
|
|
.output :
|
|
Nibble n, current value of emulated register
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_READ
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
Nibble Serial_RCS_Read( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_RCS_Read" );
|
|
debug2( DEBUG_C_SERIAL, SERIAL_I_READ, "RCS", rcs );
|
|
|
|
return rcs;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_TCS_Read
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a read of the serial transmitter control & status
|
|
register and returns its current value to the caller.
|
|
|
|
Reading TCS returns the current value of the tcs emulation register
|
|
and does not trigger any ancillary action.
|
|
|
|
.call :
|
|
n = Serial_TCS_Read();
|
|
.input :
|
|
void
|
|
.output :
|
|
Nibble n, current value of emulated register
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_READ
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
Nibble Serial_TCS_Read( void )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_TCS_Read" );
|
|
debug2( DEBUG_C_SERIAL, SERIAL_I_READ, "TCS", tcs );
|
|
|
|
return tcs;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_RBR_Read
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a read of the serial receiver buffer
|
|
register and returns its current value to the caller.
|
|
|
|
Reading RBR triggers the following additional actions:
|
|
- pulls one character from receiver ring buffer
|
|
- UpdateRCS
|
|
- CheckIRQ
|
|
|
|
.call :
|
|
d = Serial_RBR_Read();
|
|
.input :
|
|
void
|
|
.output :
|
|
int8 d, current value of emulated register
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_RBR
|
|
SERIAL_W_EMPTY_RRB
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
3.2, 22-Sep-2000, update
|
|
- conditionally (#ifdef HP49_SUPPORT) removed warning message
|
|
when reading from an empty RRB.
|
|
.- */
|
|
int8 Serial_RBR_Read( void )
|
|
{
|
|
int8 rx;
|
|
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_RBR_Read" );
|
|
|
|
/* Pull one character from rbr, if not empty */
|
|
if ( FullSlots( rrb ) > 0 ) {
|
|
Pull( rrb, &rx );
|
|
} else {
|
|
/* rrb is empty */
|
|
|
|
#ifndef HP49_SUPPORT
|
|
/* 3.2: The HP49 firmware (1.19-4) can read from an empty RRB;
|
|
this is not harmful, and this warning can be removed.
|
|
*/
|
|
ChfCondition SERIAL_W_EMPTY_RRB, CHF_WARNING, rcs ChfEnd;
|
|
ChfSignal();
|
|
#endif
|
|
|
|
rx = ( int8 )0xFF;
|
|
}
|
|
|
|
/* Update receiver status */
|
|
UpdateRCS;
|
|
|
|
/* Post a new IRQ if necessary */
|
|
CheckIRQ;
|
|
|
|
debug1( DEBUG_C_SERIAL, SERIAL_I_RBR, rx );
|
|
return rx;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_IOC_Write
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a write into the serial I/O and interrupt control
|
|
register.
|
|
|
|
Writing IOC triggers the following additional actions:
|
|
|
|
- CheckIRQ is executed
|
|
|
|
.call :
|
|
Serial_IOC_Write(n);
|
|
.input :
|
|
Nibble n, value to be written into the emulated register
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_WRITE
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
void Serial_IOC_Write( Nibble n )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_IOC_Write" );
|
|
debug3( DEBUG_C_SERIAL, SERIAL_I_WRITE, "IOC", ioc, n );
|
|
|
|
ioc = n;
|
|
|
|
CheckIRQ;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_RCS_Write
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a write into the serial receiver control & status
|
|
register.
|
|
|
|
The status is updated and no additional actions are taken; it is not
|
|
so clear when a direct write into RCS could be useful.
|
|
|
|
.call :
|
|
Serial_RCS_Write(n);
|
|
.input :
|
|
Nibble n, value to be written into the emulated register
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_WRITE
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
void Serial_RCS_Write( Nibble n )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_RCS_Write" );
|
|
debug3( DEBUG_C_SERIAL, SERIAL_I_WRITE, "RCS", rcs, n );
|
|
|
|
rcs = n;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_TCS_Write
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a write into the serial transmitter control & status
|
|
register.
|
|
|
|
The status is updated and no additional actions are taken; it is not
|
|
so clear when a direct write into TCS could be useful.
|
|
|
|
.call :
|
|
Serial_TCS_Write(n);
|
|
.input :
|
|
Nibble n, value to be written into the emulated register
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_WRITE
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
void Serial_TCS_Write( Nibble n )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_TCS_Write" );
|
|
debug3( DEBUG_C_SERIAL, SERIAL_I_WRITE, "TCS", tcs, n );
|
|
|
|
tcs = n;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_CRER_Write
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a write into the serial 'clear receiver error'
|
|
register.
|
|
|
|
The value written is ignored, and the RCS_RER bit is cleared.
|
|
|
|
.call :
|
|
Serial_CRER_Write(n);
|
|
.input :
|
|
Nibble n, value to be written into the emulated register
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_WRITE
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
void Serial_CRER_Write( Nibble n )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_CRER_Write" );
|
|
debug3( DEBUG_C_SERIAL, SERIAL_I_WRITE, "CRER", 0, n );
|
|
|
|
rcs &= ~RCS_RER;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : Serial_TBR_Write
|
|
.kind : C function
|
|
.creation : 13-Sep-2000
|
|
.description :
|
|
This function emulates a write into the serial transmitter buffer
|
|
register.
|
|
|
|
Writing RBR triggers the following additional actions:
|
|
- pushes one character into transmitter ring buffer
|
|
- UpdateTCS
|
|
- CheckIRQ
|
|
|
|
.call :
|
|
Serial_TBR_Write(d);
|
|
.input :
|
|
int8 d, value to be written into the emulated register
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_I_TBR
|
|
SERIAL_W_FULL_TRB
|
|
.notes :
|
|
2.5, 13-Sep-2000, creation
|
|
|
|
.- */
|
|
void Serial_TBR_Write( int8 d )
|
|
{
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "Serial_TBR_Write" );
|
|
debug1( DEBUG_C_SERIAL, SERIAL_I_TBR, d );
|
|
|
|
/* Pull one character from rbr, if not empty */
|
|
if ( EmptySlots( trb ) > 0 ) {
|
|
Push( trb, d );
|
|
} else {
|
|
/* trb is full; discard character */
|
|
ChfCondition SERIAL_W_FULL_TRB, CHF_WARNING, tcs ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Update transmitter status */
|
|
UpdateTCS;
|
|
|
|
/* Post a new IRQ if necessary */
|
|
CheckIRQ;
|
|
}
|
|
|
|
/* .+
|
|
|
|
.title : HandleSerial
|
|
.kind : C function
|
|
.creation : 14-Sep-2000
|
|
.description :
|
|
This function is called by the emulator loop every 1/16s, and performs
|
|
the following actions:
|
|
|
|
- attempt to pull as many characters as possible from the transmitter
|
|
ring buffer and to write them on master_pty (PullAndWrite)
|
|
- UpdateTCS
|
|
|
|
- attempt to read as many characters are possible from master_pty and
|
|
to push them into the receiver ring buffer (ReadAndPush)
|
|
- UpdateRCS
|
|
|
|
- CheckIRQ
|
|
|
|
.call :
|
|
HandleSerial();
|
|
.input :
|
|
void
|
|
.output :
|
|
void
|
|
.status_codes :
|
|
SERIAL_I_CALLED
|
|
SERIAL_E_TRB_DRAIN
|
|
SERIAL_E_RRB_CHARGE
|
|
.notes :
|
|
2.5, 14-Sep-2000, creation
|
|
2.6, 15-Sep-2000, update
|
|
- enhanced documentation of status_codes
|
|
3.16, 16-Nov-2000, update
|
|
- added dummy pty implementation
|
|
3.17, 22-Nov-2000, bug fix
|
|
- transmit ring buffer must be emptied even when !IOC_SON
|
|
.- */
|
|
void HandleSerial( void )
|
|
{
|
|
int result;
|
|
|
|
debug1( DEBUG_C_TRACE, SERIAL_I_CALLED, "HandleSerial" );
|
|
|
|
#ifndef USE_NOPTY
|
|
/* Attempt to drain transmitter buffer even if serial port is closed */
|
|
result = PullAndWrite( &trb, master_pty );
|
|
|
|
/* Signal a condition upon failure */
|
|
if ( result < 0 ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_E_TRB_DRAIN, CHF_ERROR ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Update tcs */
|
|
UpdateTCS;
|
|
|
|
if ( ioc & IOC_SON ) {
|
|
/* Attempt to charge receiver buffer */
|
|
result = ReadAndPush( &rrb, master_pty );
|
|
|
|
/* Signal a condition upon failure */
|
|
if ( result < 0 ) {
|
|
ChfErrnoCondition;
|
|
ChfCondition SERIAL_E_RRB_CHARGE, CHF_ERROR ChfEnd;
|
|
ChfSignal();
|
|
}
|
|
|
|
/* Update receiver status */
|
|
UpdateRCS;
|
|
|
|
/* Post an IRQ if necessary */
|
|
CheckIRQ;
|
|
}
|
|
#endif
|
|
}
|