/** ** @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 // all other framework includes are next #include #include #include #include #include /* ** 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" ); } }