diff options
Diffstat (limited to 'kernel/sio.c')
-rw-r--r-- | kernel/sio.c | 694 |
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" ); + } +} |