summaryrefslogtreecommitdiff
path: root/kernel/sio.c
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-03-25 17:36:52 -0400
committerFreya Murphy <freya@freyacat.org>2025-03-25 17:38:22 -0400
commit6af21e6a4f2251e71353562d5df7f376fdffc270 (patch)
treede20c7afc9878422c81e34f30c6b010075e9e69a /kernel/sio.c
downloadcomus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.gz
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.bz2
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.zip
initial checkout from wrc
Diffstat (limited to '')
-rw-r--r--kernel/sio.c694
1 files changed, 694 insertions, 0 deletions
diff --git a/kernel/sio.c b/kernel/sio.c
new file mode 100644
index 0000000..a5c7b75
--- /dev/null
+++ b/kernel/sio.c
@@ -0,0 +1,694 @@
+/**
+** @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
+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" );
+ }
+}