mirror of
https://github.com/kenshineto/kern.git
synced 2025-04-20 17:27:24 +00:00
694 lines
14 KiB
C
694 lines
14 KiB
C
/**
|
|
** @file sio.c
|
|
**
|
|
** @author Warren R. Carithers
|
|
**
|
|
** @brief SIO module
|
|
**
|
|
** For maximum compatibility from semester to semester, this code uses
|
|
** several "stand-in" type names and macros which should be defined
|
|
** in the accompanying "compat.h" header file if they're not part of
|
|
** the baseline system:
|
|
**
|
|
** standard-sized integer types: intN_t, uintN_t
|
|
** other types: PCBTYPE, QTYPE
|
|
** scheduler functions: SCHED, DISPATCH
|
|
** queue functions: QCREATE, QLENGTH, QDEQUE
|
|
** other functions: SLENGTH
|
|
** sio read queue: QNAME
|
|
**
|
|
** Our SIO scheme is very simple:
|
|
**
|
|
** Input: We maintain a buffer of incoming characters that haven't
|
|
** yet been read by processes. When a character comes in, if
|
|
** there is no process waiting for it, it goes in the buffer;
|
|
** otherwise, the first waiting process is awakeneda and it
|
|
** gets the character.
|
|
**
|
|
** When a process invokes readch(), if there is a character in
|
|
** the input buffer, the process gets it; otherwise, it is
|
|
** blocked until input appears
|
|
**
|
|
** Communication with system calls is via two routines.
|
|
** sio_readc() returns the first available character (if
|
|
** there is one), resetting the input variables if this was
|
|
** the last character in the buffer. If there are no
|
|
** characters in the buffer, sio_read() returns a -1
|
|
** (presumably so the requesting process can be blocked).
|
|
**
|
|
** sio_read() copies the contents of the input buffer into
|
|
** a user-supplied buffer. It returns the number of characters
|
|
** copied. If there are no characters available, return a -1.
|
|
**
|
|
** Output: We maintain a buffer of outgoing characters that haven't
|
|
** yet been sent to the device, and an indication of whether
|
|
** or not we are in the middle of a transmit sequence. When
|
|
** an interrupt comes in, if there is another character to
|
|
** send we copy it to the transmitter buffer; otherwise, we
|
|
** end the transmit sequence.
|
|
**
|
|
** Communication with user processes is via three functions.
|
|
** sio_writec() writes a single character; sio_write()
|
|
** writes a sized buffer full of characters; sio_puts()
|
|
** prints a NUL-terminated string. If we are in the middle
|
|
** of a transmit sequence, all characters will be added
|
|
** to the output buffer (from where they will be sent
|
|
** automatically); otherwise, we send the first character
|
|
** directly, add the rest of the characters (if there are
|
|
** any) to the output buffer, and set the "sending" flag
|
|
** to indicate that we're expecting a transmitter interrupt.
|
|
*/
|
|
|
|
#define KERNEL_SRC
|
|
|
|
// this should do all includes required for this OS
|
|
#include <compat.h>
|
|
|
|
// all other framework includes are next
|
|
#include <x86/uart.h>
|
|
#include <x86/arch.h>
|
|
#include <x86/pic.h>
|
|
|
|
#include <sio.h>
|
|
#include <lib.h>
|
|
|
|
/*
|
|
** PRIVATE DEFINITIONS
|
|
*/
|
|
|
|
#define BUF_SIZE 2048
|
|
|
|
/*
|
|
** PRIVATE GLOBALS
|
|
*/
|
|
|
|
// input character buffer
|
|
static char inbuffer[ BUF_SIZE ];
|
|
static char *inlast;
|
|
static char *innext;
|
|
static uint32_t incount;
|
|
|
|
// output character buffer
|
|
static char outbuffer[ BUF_SIZE ];
|
|
static char *outlast;
|
|
static char *outnext;
|
|
static uint32_t outcount;
|
|
|
|
// output control flag
|
|
static int sending;
|
|
|
|
// interrupt register status
|
|
static uint8_t ier;
|
|
|
|
/*
|
|
** PUBLIC GLOBAL VARIABLES
|
|
*/
|
|
|
|
// queue for read-blocked processes
|
|
#ifdef QNAME
|
|
extern QTYPE QNAME;
|
|
#endif
|
|
|
|
/*
|
|
** PRIVATE FUNCTIONS
|
|
*/
|
|
|
|
/**
|
|
** sio_isr(vector,ecode)
|
|
**
|
|
** Interrupt handler for the SIO module. Handles all pending
|
|
** events (as described by the SIO controller).
|
|
**
|
|
** @param vector The interrupt vector number for this interrupt
|
|
** @param ecode The error code associated with this interrupt
|
|
*/
|
|
static void sio_isr( int vector, int ecode ) {
|
|
int ch;
|
|
|
|
#if TRACING_SIO_ISR
|
|
cio_puts( "SIO: int:" );
|
|
#endif
|
|
//
|
|
// Must process all pending events; loop until the IRR
|
|
// says there's nothing else to do.
|
|
//
|
|
|
|
for(;;) {
|
|
|
|
// get the "pending event" indicator
|
|
int iir = inb( UA4_IIR ) & UA4_IIR_INT_PRI_MASK;
|
|
|
|
// process this event
|
|
switch( iir ) {
|
|
|
|
case UA4_IIR_LINE_STATUS:
|
|
// shouldn't happen, but just in case....
|
|
cio_printf( "** SIO int, LSR = %02x\n", inb(UA4_LSR) );
|
|
break;
|
|
|
|
case UA4_IIR_RX:
|
|
#if TRACING_SIO_ISR
|
|
cio_puts( " RX" );
|
|
#endif
|
|
// get the character
|
|
ch = inb( UA4_RXD );
|
|
if( ch == '\r' ) { // map CR to LF
|
|
ch = '\n';
|
|
}
|
|
#if TRACING_SIO_ISR
|
|
cio_printf( " ch %02x", ch );
|
|
#endif
|
|
|
|
#ifdef QNAME
|
|
//
|
|
// If there is a waiting process, this must be
|
|
// the first input character; give it to that
|
|
// process and awaken the process.
|
|
//
|
|
|
|
if( !QEMPTY(QNAME) ) {
|
|
PCBTYPE *pcb;
|
|
|
|
QDEQUE( QNAME, pcb );
|
|
// make sure we got a non-NULL result
|
|
assert( pcb );
|
|
|
|
// return char via arg #2 and count in EAX
|
|
char *buf = (char *) ARG(pcb,2);
|
|
*buf = ch & 0xff;
|
|
RET(pcb) = 1;
|
|
SCHED( pcb );
|
|
|
|
} else {
|
|
#endif /* QNAME */
|
|
|
|
//
|
|
// Nobody waiting - add to the input buffer
|
|
// if there is room, otherwise just ignore it.
|
|
//
|
|
|
|
if( incount < BUF_SIZE ) {
|
|
*inlast++ = ch;
|
|
++incount;
|
|
}
|
|
|
|
#ifdef QNAME
|
|
}
|
|
#endif /* QNAME */
|
|
break;
|
|
|
|
case UA5_IIR_RX_FIFO:
|
|
// shouldn't happen, but just in case....
|
|
ch = inb( UA4_RXD );
|
|
cio_printf( "** SIO FIFO timeout, RXD = %02x\n", ch );
|
|
break;
|
|
|
|
case UA4_IIR_TX:
|
|
#if TRACING_SIO_ISR
|
|
cio_puts( " TX" );
|
|
#endif
|
|
// if there is another character, send it
|
|
if( sending && outcount > 0 ) {
|
|
#if TRACING_SIO_ISR
|
|
cio_printf( " ch %02x", *outnext );
|
|
#endif
|
|
outb( UA4_TXD, *outnext );
|
|
++outnext;
|
|
// wrap around if necessary
|
|
if( outnext >= (outbuffer + BUF_SIZE) ) {
|
|
outnext = outbuffer;
|
|
}
|
|
--outcount;
|
|
#if TRACING_SIO_ISR
|
|
cio_printf( " (outcount %d)", outcount );
|
|
#endif
|
|
} else {
|
|
#if TRACING_SIO_ISR
|
|
cio_puts( " EOS" );
|
|
#endif
|
|
// no more data - reset the output vars
|
|
outcount = 0;
|
|
outlast = outnext = outbuffer;
|
|
sending = 0;
|
|
// disable TX interrupts
|
|
sio_disable( SIO_TX );
|
|
}
|
|
break;
|
|
|
|
case UA4_IIR_NO_INT:
|
|
#if TRACING_SIO_ISR
|
|
cio_puts( " EOI\n" );
|
|
#endif
|
|
// nothing to do - tell the PIC we're done
|
|
outb( PIC1_CMD, PIC_EOI );
|
|
return;
|
|
|
|
case UA4_IIR_MODEM_STATUS:
|
|
// shouldn't happen, but just in case....
|
|
cio_printf( "** SIO int, MSR = %02x\n", inb(UA4_MSR) );
|
|
break;
|
|
|
|
default:
|
|
// uh-oh....
|
|
sprint( b256, "sio isr: IIR %02x\n", ((uint32_t) iir) & 0xff );
|
|
PANIC( 0, b256 );
|
|
}
|
|
|
|
}
|
|
|
|
// should never reach this point!
|
|
assert( false );
|
|
}
|
|
|
|
/*
|
|
** PUBLIC FUNCTIONS
|
|
*/
|
|
|
|
/**
|
|
** sio_init()
|
|
**
|
|
** Initialize the UART chip.
|
|
*/
|
|
void sio_init( void ) {
|
|
|
|
#if TRACING_INIT
|
|
cio_puts( " Sio" );
|
|
#endif
|
|
|
|
/*
|
|
** Initialize SIO variables.
|
|
*/
|
|
|
|
memclr( (void *) inbuffer, sizeof(inbuffer) );
|
|
inlast = innext = inbuffer;
|
|
incount = 0;
|
|
|
|
memclr( (void *) outbuffer, sizeof(outbuffer) );
|
|
outlast = outnext = outbuffer;
|
|
outcount = 0;
|
|
sending = 0;
|
|
|
|
// queue of read-blocked processes
|
|
QCREATE( QNAME );
|
|
|
|
/*
|
|
** Next, initialize the UART.
|
|
**
|
|
** Initialize the FIFOs
|
|
**
|
|
** this is a bizarre little sequence of operations
|
|
*/
|
|
|
|
outb( UA5_FCR, 0x20 );
|
|
outb( UA5_FCR, UA5_FCR_FIFO_RESET ); // 0x00
|
|
outb( UA5_FCR, UA5_FCR_FIFO_EN ); // 0x01
|
|
outb( UA5_FCR, UA5_FCR_FIFO_EN | UA5_FCR_RXSR ); // 0x03
|
|
outb( UA5_FCR, UA5_FCR_FIFO_EN | UA5_FCR_RXSR | UA5_FCR_TXSR ); // 0x07
|
|
|
|
/*
|
|
** disable interrupts
|
|
**
|
|
** note that we leave them disabled; sio_enable() must be
|
|
** called to switch them back on
|
|
*/
|
|
|
|
outb( UA4_IER, 0 );
|
|
ier = 0;
|
|
|
|
/*
|
|
** select the divisor latch registers and set the data rate
|
|
*/
|
|
|
|
outb( UA4_LCR, UA4_LCR_DLAB );
|
|
outb( UA4_DLL, BAUD_LOW_BYTE( DL_BAUD_9600 ) );
|
|
outb( UA4_DLM, BAUD_HIGH_BYTE( DL_BAUD_9600 ) );
|
|
|
|
/*
|
|
** deselect the latch registers, by setting the data
|
|
** characteristics in the LCR
|
|
*/
|
|
|
|
outb( UA4_LCR, UA4_LCR_WLS_8 | UA4_LCR_1_STOP_BIT | UA4_LCR_NO_PARITY );
|
|
|
|
/*
|
|
** Set the ISEN bit to enable the interrupt request signal,
|
|
** and the DTR and RTS bits to enable two-way communication.
|
|
*/
|
|
|
|
outb( UA4_MCR, UA4_MCR_ISEN | UA4_MCR_DTR | UA4_MCR_RTS );
|
|
|
|
/*
|
|
** Install our ISR
|
|
*/
|
|
|
|
install_isr( VEC_COM1, sio_isr );
|
|
}
|
|
|
|
/**
|
|
** sio_enable()
|
|
**
|
|
** Enable SIO interrupts
|
|
**
|
|
** usage: uint8_t old = sio_enable( uint8_t which )
|
|
**
|
|
** @param which Bit mask indicating which interrupt(s) to enable
|
|
**
|
|
** @return the prior IER setting
|
|
*/
|
|
uint8_t sio_enable( uint8_t which ) {
|
|
uint8_t old;
|
|
|
|
// remember the current status
|
|
|
|
old = ier;
|
|
|
|
// figure out what to enable
|
|
|
|
if( which & SIO_TX ) {
|
|
ier |= UA4_IER_TX_IE;
|
|
}
|
|
|
|
if( which & SIO_RX ) {
|
|
ier |= UA4_IER_RX_IE;
|
|
}
|
|
|
|
// if there was a change, make it
|
|
|
|
if( old != ier ) {
|
|
outb( UA4_IER, ier );
|
|
}
|
|
|
|
// return the prior settings
|
|
|
|
return( old );
|
|
}
|
|
|
|
/**
|
|
** sio_disable()
|
|
**
|
|
** Disable SIO interrupts
|
|
**
|
|
** usage: uint8_t old = sio_disable( uint8_t which )
|
|
**
|
|
** @param which Bit mask indicating which interrupt(s) to disable
|
|
**
|
|
** @return the prior IER setting
|
|
*/
|
|
uint8_t sio_disable( uint8_t which ) {
|
|
uint8_t old;
|
|
|
|
// remember the current status
|
|
|
|
old = ier;
|
|
|
|
// figure out what to disable
|
|
|
|
if( which & SIO_TX ) {
|
|
ier &= ~UA4_IER_TX_IE;
|
|
}
|
|
|
|
if( which & SIO_RX ) {
|
|
ier &= ~UA4_IER_RX_IE;
|
|
}
|
|
|
|
// if there was a change, make it
|
|
|
|
if( old != ier ) {
|
|
outb( UA4_IER, ier );
|
|
}
|
|
|
|
// return the prior settings
|
|
|
|
return( old );
|
|
}
|
|
|
|
/**
|
|
** sio_inq_length()
|
|
**
|
|
** Get the input queue length
|
|
**
|
|
** usage: int num = sio_inq_length()
|
|
**
|
|
** @return the count of characters still in the input queue
|
|
*/
|
|
int sio_inq_length( void ) {
|
|
return( incount );
|
|
}
|
|
|
|
/**
|
|
** sio_readc()
|
|
**
|
|
** Get the next input character
|
|
**
|
|
** usage: int ch = sio_readc()
|
|
**
|
|
** @return the next character, or -1 if no character is available
|
|
*/
|
|
int sio_readc( void ) {
|
|
int ch;
|
|
|
|
// assume there is no character available
|
|
ch = -1;
|
|
|
|
//
|
|
// If there is a character, return it
|
|
//
|
|
|
|
if( incount > 0 ) {
|
|
|
|
// take it out of the input buffer
|
|
ch = ((int)(*innext++)) & 0xff;
|
|
--incount;
|
|
|
|
// reset the buffer variables if this was the last one
|
|
if( incount < 1 ) {
|
|
inlast = innext = inbuffer;
|
|
}
|
|
|
|
}
|
|
|
|
return( ch );
|
|
|
|
}
|
|
|
|
/**
|
|
** sio_read(buf,length)
|
|
**
|
|
** Read the entire input buffer into a user buffer of a specified size
|
|
**
|
|
** usage: int num = sio_read( char *buffer, int length )
|
|
**
|
|
** @param buf The destination buffer
|
|
** @param length Length of the buffer
|
|
**
|
|
** @return the number of bytes copied, or 0 if no characters were available
|
|
*/
|
|
|
|
int sio_read( char *buf, int length ) {
|
|
char *ptr = buf;
|
|
int copied = 0;
|
|
|
|
// if there are no characters, just return 0
|
|
|
|
if( incount < 1 ) {
|
|
return( 0 );
|
|
}
|
|
|
|
//
|
|
// We have characters. Copy as many of them into the user
|
|
// buffer as will fit.
|
|
//
|
|
|
|
while( incount > 0 && copied < length ) {
|
|
*ptr++ = *innext++ & 0xff;
|
|
if( innext > (inbuffer + BUF_SIZE) ) {
|
|
innext = inbuffer;
|
|
}
|
|
--incount;
|
|
++copied;
|
|
}
|
|
|
|
// reset the input buffer if necessary
|
|
|
|
if( incount < 1 ) {
|
|
inlast = innext = inbuffer;
|
|
}
|
|
|
|
// return the copy count
|
|
|
|
return( copied );
|
|
}
|
|
|
|
|
|
/**
|
|
** sio_writec( ch )
|
|
**
|
|
** Write a character to the serial output
|
|
**
|
|
** usage: sio_writec( int ch )
|
|
**
|
|
** @param ch Character to be written (in the low-order 8 bits)
|
|
*/
|
|
void sio_writec( int ch ){
|
|
|
|
|
|
//
|
|
// Must do LF -> CRLF mapping
|
|
//
|
|
|
|
if( ch == '\n' ) {
|
|
sio_writec( '\r' );
|
|
}
|
|
|
|
//
|
|
// If we're currently transmitting, just add this to the buffer
|
|
//
|
|
|
|
if( sending ) {
|
|
*outlast++ = ch;
|
|
++outcount;
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Not sending - must prime the pump
|
|
//
|
|
|
|
sending = 1;
|
|
outb( UA4_TXD, ch );
|
|
|
|
// Also must enable transmitter interrupts
|
|
|
|
sio_enable( SIO_TX );
|
|
|
|
}
|
|
|
|
/**
|
|
** sio_write( buffer, length )
|
|
**
|
|
** Write a buffer of characters to the serial output
|
|
**
|
|
** usage: int num = sio_write( const char *buffer, int length )
|
|
**
|
|
** @param buffer Buffer containing characters to write
|
|
** @param length Number of characters to write
|
|
**
|
|
** @return the number of characters copied into the SIO output buffer
|
|
*/
|
|
int sio_write( const char *buffer, int length ) {
|
|
int first = *buffer;
|
|
const char *ptr = buffer;
|
|
int copied = 0;
|
|
|
|
//
|
|
// If we are currently sending, we want to append all
|
|
// the characters to the output buffer; else, we want
|
|
// to append all but the first character, and then use
|
|
// sio_writec() to send the first one out.
|
|
//
|
|
|
|
if( !sending ) {
|
|
ptr += 1;
|
|
copied++;
|
|
}
|
|
|
|
while( copied < length && outcount < BUF_SIZE ) {
|
|
*outlast++ = *ptr++;
|
|
// wrap around if necessary
|
|
if( outlast >= (outbuffer + BUF_SIZE) ) {
|
|
outlast = outbuffer;
|
|
}
|
|
++outcount;
|
|
++copied;
|
|
}
|
|
|
|
//
|
|
// We use sio_writec() to send out the first character,
|
|
// as it will correctly set all the other necessary
|
|
// variables for us.
|
|
//
|
|
|
|
if( !sending ) {
|
|
sio_writec( first );
|
|
}
|
|
|
|
// Return the transfer count
|
|
|
|
|
|
return( copied );
|
|
|
|
}
|
|
|
|
/**
|
|
** sio_puts( buf )
|
|
**
|
|
** Write a NUL-terminated buffer of characters to the serial output
|
|
**
|
|
** usage: int num = sio_puts( const char *buffer )
|
|
**
|
|
** @param buffer The buffer containing a NUL-terminated string
|
|
**
|
|
** @return the count of bytes transferred
|
|
*/
|
|
int sio_puts( const char *buffer ) {
|
|
int n; // must be outside the loop so we can return it
|
|
|
|
n = SLENGTH( buffer );
|
|
sio_write( buffer, n );
|
|
|
|
return( n );
|
|
}
|
|
|
|
/**
|
|
** sio_dump( full )
|
|
**
|
|
** dump the contents of the SIO buffers to the console
|
|
**
|
|
** usage: sio_dump(true) or sio_dump(false)
|
|
**
|
|
** @param full Boolean indicating whether or not a "full" dump
|
|
** is being requested (which includes the contents
|
|
** of the queues)
|
|
*/
|
|
|
|
void sio_dump( bool_t full ) {
|
|
int n;
|
|
char *ptr;
|
|
|
|
// dump basic info into the status region
|
|
|
|
cio_printf_at( 48, 0,
|
|
"SIO: IER %02x (%c%c%c) in %d ot %d",
|
|
((uint32_t)ier) & 0xff, sending ? '*' : '.',
|
|
(ier & UA4_IER_TX_IE) ? 'T' : 't',
|
|
(ier & UA4_IER_RX_IE) ? 'R' : 'r',
|
|
incount, outcount );
|
|
|
|
// if we're not doing a full dump, stop now
|
|
|
|
if( !full ) {
|
|
return;
|
|
}
|
|
|
|
// also want the queue contents, but we'll
|
|
// dump them into the scrolling region
|
|
|
|
if( incount ) {
|
|
cio_puts( "SIO input queue: \"" );
|
|
ptr = innext;
|
|
for( n = 0; n < incount; ++n ) {
|
|
put_char_or_code( *ptr++ );
|
|
}
|
|
cio_puts( "\"\n" );
|
|
}
|
|
|
|
if( outcount ) {
|
|
cio_puts( "SIO output queue: \"" );
|
|
cio_puts( " ot: \"" );
|
|
ptr = outnext;
|
|
for( n = 0; n < outcount; ++n ) {
|
|
put_char_or_code( *ptr++ );
|
|
}
|
|
cio_puts( "\"\n" );
|
|
}
|
|
}
|