summaryrefslogtreecommitdiff
path: root/kernel/old/drivers/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/old/drivers/serial.c')
-rw-r--r--kernel/old/drivers/serial.c629
1 files changed, 629 insertions, 0 deletions
diff --git a/kernel/old/drivers/serial.c b/kernel/old/drivers/serial.c
new file mode 100644
index 0000000..d6572e5
--- /dev/null
+++ b/kernel/old/drivers/serial.c
@@ -0,0 +1,629 @@
+#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");
+ }
+}