X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdevices%2Fserial.c;h=d92a87ce1540be2af42940622491a4a28be078c2;hb=615bf3b3d2a8573ed6fb9ddc0055745e163ac999;hp=a27c50a0ab835fc02e888ebce02b9828054fa7c9;hpb=4dd8ec21a0cee6aa8aef927fc956eba232e86a51;p=pintos-anon diff --git a/src/devices/serial.c b/src/devices/serial.c index a27c50a..d92a87c 100644 --- a/src/devices/serial.c +++ b/src/devices/serial.c @@ -1,38 +1,57 @@ #include "devices/serial.h" #include -#include "devices/16550a.h" +#include "devices/intq.h" #include "devices/timer.h" #include "threads/io.h" #include "threads/interrupt.h" #include "threads/synch.h" #include "threads/thread.h" + +/* Register definitions for the 16550A UART used in PCs. + The 16550A has a lot more going on than shown here, but this + is all we need. -#define QUEUE_BUFSIZE 8 + Refer to [PC16650D] for hardware information. */ -struct queue - { - enum intr_level old_level; - struct lock lock; - - struct thread *not_full, *not_empty; - uint8_t buf[QUEUE_BUFSIZE]; - int head, tail; - }; +/* I/O port base address for the first serial port. */ +#define IO_BASE 0x3f8 -static struct queue recv_queue, xmit_queue; -static bool polled_io; +/* DLAB=0 registers. */ +#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */ +#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */ +#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg. (read-only). */ +#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */ +#define LCR_REG (IO_BASE + 3) /* Line Control Register. */ +#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */ +#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */ -static void set_serial (int bps, int bits, enum parity_type parity, int stop); -static void update_ier (void); -static intr_handler_func serial_interrupt; +/* DLAB=1 registers. */ +#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */ +#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */ + +/* Interrupt Enable Register bits. */ +#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */ + +/* Line Control Register bits. */ +#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */ +#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */ + +/* MODEM Control Register. */ +#define MCR_OUT2 0x08 /* Output line 2. */ + +/* Line Status Register. */ +#define LSR_THRE 0x20 /* THR Empty. */ + +/* Transmission mode. */ +static enum { UNINIT, POLL, QUEUE } mode; + +/* Data to be transmitted. */ +static struct intq txq; -static void queue_init (struct queue *, const char *); -static void queue_lock (struct queue *); -static void queue_unlock (struct queue *); -static bool queue_empty (const struct queue *); -static bool queue_full (const struct queue *); -static uint8_t queue_getc (struct queue *); -static void queue_putc (struct queue *, uint8_t); +static void set_serial (int bps); +static void putc_poll (uint8_t); +static void write_ier (void); +static intr_handler_func serial_interrupt; /* Initializes the serial port device for polling mode. Polling mode busy-waits for the serial port to become free @@ -41,12 +60,13 @@ static void queue_putc (struct queue *, uint8_t); void serial_init_poll (void) { - ASSERT (!polled_io); - polled_io = true; + ASSERT (mode == UNINIT); outb (IER_REG, 0); /* Turn off all interrupts. */ outb (FCR_REG, 0); /* Disable FIFO. */ - set_serial (9600, 8, NONE, 1); /* 9600 bps, N-8-1. */ - outb (MCR_REG, 8); /* Turn on OUT2 output line. */ + set_serial (115200); /* 115.2 kbps, N-8-1. */ + outb (MCR_REG, MCR_OUT2); /* Turn on OUT2 output line. */ + intq_init (&txq); + mode = POLL; } /* Initializes the serial port device for queued interrupt-driven @@ -55,222 +75,105 @@ serial_init_poll (void) void serial_init_queue (void) { - ASSERT (polled_io); - polled_io = false; - queue_init (&recv_queue, "serial recv"); - queue_init (&xmit_queue, "serial xmit"); - intr_register (0x20 + 4, 0, INTR_OFF, serial_interrupt, "serial"); + ASSERT (mode == POLL); + intr_register_ext (0x20 + 4, serial_interrupt, "serial"); + mode = QUEUE; } -static void putc_poll (uint8_t); -static void putc_queue (uint8_t); - /* Sends BYTE to the serial port. */ void serial_putc (uint8_t byte) { - if (polled_io || intr_context ()) - putc_poll (byte); - else - putc_queue (byte); -} - -static void -putc_poll (uint8_t byte) -{ - while ((inb (LSR_REG) & LSR_THRE) == 0) - continue; - outb (THR_REG, byte); -} - -static void -putc_queue (uint8_t byte) -{ - queue_lock (&xmit_queue); + enum intr_level old_level = intr_disable (); - queue_putc (&xmit_queue, byte); - update_ier (); - - queue_unlock (&xmit_queue); + if (mode == POLL) + { + /* If we're not set up for interrupt-driven I/O yet, + use dumb polling to transmit a byte. */ + putc_poll (byte); + } + else + { + /* Otherwise, queue a byte and update the interrupt enable + register. */ + if (old_level == INTR_OFF && intq_full (&txq)) + { + /* Interrupts are off and the transmit queue is full. + If we wanted to wait for the queue to empty, + we'd have to reenable interrupts. + That's impolite, so we'll send a character via + polling instead. */ + putc_poll (intq_getc (&txq)); + } + + intq_putc (&txq, byte); + write_ier (); + } + + intr_set_level (old_level); } -/* Reads and returns a byte from the serial port. */ -uint8_t -serial_getc (void) +/* Flushes anything in the serial buffer out the port in polling + mode. */ +void +serial_flush (void) { - uint8_t byte; - queue_lock (&recv_queue); - - /* Read a byte. */ - byte = queue_getc (&recv_queue); - update_ier (); - - queue_unlock (&recv_queue); - return byte; + enum intr_level old_level = intr_disable (); + while (!intq_empty (&txq)) + putc_poll (intq_getc (&txq)); + intr_set_level (old_level); } - -/* Configures the first serial port for BPS bits per second, - BITS bits per byte, the given PARITY, and STOP stop bits. */ + +/* Configures the serial port for BPS bits per second. */ static void -set_serial (int bps, int bits, enum parity_type parity, int stop) +set_serial (int bps) { int baud_base = 1843200 / 16; /* Base rate of 16550A. */ uint16_t divisor = baud_base / bps; /* Clock rate divisor. */ + ASSERT (bps >= 300 && bps <= 115200); + /* Enable DLAB. */ - outb (LCR_REG, make_lcr (bits, parity, stop, false, true)); + outb (LCR_REG, LCR_N81 | LCR_DLAB); /* Set baud rate. */ outb (LS_REG, divisor & 0xff); outb (MS_REG, divisor >> 8); /* Reset DLAB. */ - outb (LCR_REG, make_lcr (bits, parity, stop, false, false)); + outb (LCR_REG, LCR_N81); } /* Update interrupt enable register. - If our transmit queue is empty, turn off transmit interrupt. - If our receive queue is full, turn off receive interrupt. */ + If our transmit queue is empty, turn off transmit interrupt. */ static void -update_ier (void) +write_ier (void) { - uint8_t ier = 0; - - ASSERT (intr_get_level () == INTR_OFF); - - if (!queue_empty (&xmit_queue)) - ier |= IER_XMIT; - if (!queue_full (&recv_queue)) - ier |= IER_RECV; - outb (IER_REG, ier); + outb (IER_REG, intq_empty (&txq) ? 0 : IER_XMIT); } +/* Polls the serial port until it's ready, + and then transmits BYTE. */ static void -serial_interrupt (struct intr_frame *f UNUSED) -{ - /* As long as we have space in our buffer for a byte, - and the hardware has a byte to give us, - receive a byte. */ - while (!queue_full (&recv_queue) && (inb (LSR_REG) & LSR_DR) != 0) - { - uint8_t byte = inb (RBR_REG); - //outb (0x0402, byte); - queue_putc (&recv_queue, byte); - } - - /* As long as we have a byte to transmit, - and the hardware is ready to accept a byte for transmission, - transmit a byte. */ - while (!queue_empty (&xmit_queue) && (inb (LSR_REG) & LSR_THRE) != 0) - outb (THR_REG, queue_getc (&xmit_queue)); - - /* Update interrupt enable register based on queue status. */ - update_ier (); -} - -static int next (int pos); -static bool owned_by_current_thread (const struct queue *); -static void wait (struct queue *q, struct thread **waiter); -static void signal (struct queue *q, struct thread **waiter); - -static void -queue_init (struct queue *q, const char *name) -{ - lock_init (&q->lock, name); - q->not_full = q->not_empty = NULL; - q->head = q->tail = 0; -} - -static void -queue_lock (struct queue *q) -{ - lock_acquire (&q->lock); - q->old_level = intr_disable (); -} - -static void -queue_unlock (struct queue *q) -{ - ASSERT (!intr_context ()); - ASSERT (owned_by_current_thread (q)); - - lock_release (&q->lock); - intr_set_level (q->old_level); -} - -static bool -queue_empty (const struct queue *q) +putc_poll (uint8_t byte) { ASSERT (intr_get_level () == INTR_OFF); - return q->head == q->tail; -} -static bool -queue_full (const struct queue *q) -{ - ASSERT (intr_get_level () == INTR_OFF); - return next (q->head) == q->tail; -} - -static uint8_t -queue_getc (struct queue *q) -{ - uint8_t byte; - - ASSERT (owned_by_current_thread (q)); - while (queue_empty (q)) - wait (q, &q->not_empty); - - byte = q->buf[q->tail]; - q->tail = next (q->tail); - signal (q, &q->not_full); - return byte; -} - -static void -queue_putc (struct queue *q, uint8_t byte) -{ - ASSERT (owned_by_current_thread (q)); - while (queue_full (q)) - wait (q, &q->not_full); - - q->buf[q->head] = byte; - q->head = next (q->head); - signal (q, &q->not_empty); -} - -static int -next (int pos) -{ - return (pos + 1) % QUEUE_BUFSIZE; -} - -static bool -owned_by_current_thread (const struct queue *q) -{ - return (intr_context () - || (lock_held_by_current_thread (&q->lock) - && intr_get_level () == INTR_OFF)); -} - -static void -wait (struct queue *q, struct thread **waiter) -{ - ASSERT (!intr_context ()); - ASSERT (owned_by_current_thread (q)); - - *waiter = thread_current (); - thread_block (); + while ((inb (LSR_REG) & LSR_THRE) == 0) + continue; + outb (THR_REG, byte); } +/* Serial interrupt handler. + As long as we have a byte to transmit, + and the hardware is ready to accept a byte for transmission, + transmit a byte. + Then update interrupt enable register based on queue + status. */ static void -signal (struct queue *q, struct thread **waiter) +serial_interrupt (struct intr_frame *f UNUSED) { - ASSERT (owned_by_current_thread (q)); - - if (*waiter != NULL) - { - thread_unblock (*waiter); - *waiter = NULL; - } + while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) + outb (THR_REG, intq_getc (&txq)); + write_ier (); }