/** ** @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 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"); } }