--- /dev/null
+#include "devices/intq.h"
+#include <debug.h>
+#include "threads/thread.h"
+
+static int next (int pos);
+static bool owned_by_current_thread (const struct intq *);
+static void wait (struct intq *q, struct thread **waiter);
+static void signal (struct intq *q, struct thread **waiter);
+
+/* Initializes interrupt queue Q, naming it NAME (for debugging
+ purposes). */
+void
+intq_init (struct intq *q, const char *name)
+{
+ lock_init (&q->lock, name);
+ q->not_full = q->not_empty = NULL;
+ q->head = q->tail = 0;
+}
+
+/* Locks out other threads from Q (with Q's lock) and interrupt
+ handlers (by disabling interrupts). */
+void
+intq_lock (struct intq *q)
+{
+ ASSERT (!intr_context ());
+ ASSERT (!owned_by_current_thread (q));
+
+ lock_acquire (&q->lock);
+ q->old_level = intr_disable ();
+}
+
+/* Unlocks Q. */
+void
+intq_unlock (struct intq *q)
+{
+ ASSERT (!intr_context ());
+ ASSERT (owned_by_current_thread (q));
+
+ lock_release (&q->lock);
+ intr_set_level (q->old_level);
+}
+
+/* Returns true if Q is empty, false otherwise. */
+bool
+intq_empty (const struct intq *q)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return q->head == q->tail;
+}
+
+/* Returns true if Q is full, false otherwise. */
+bool
+intq_full (const struct intq *q)
+{
+ ASSERT (intr_get_level () == INTR_OFF);
+ return next (q->head) == q->tail;
+}
+
+/* Removes a byte from Q and returns it.
+ Q must not be empty if called from an interrupt handler.
+ Otherwise, if Q is empty, first waits until a byte is added.
+ Either Q must be locked or we must be in an interrupt
+ handler. */
+uint8_t
+intq_getc (struct intq *q)
+{
+ uint8_t byte;
+
+ ASSERT (owned_by_current_thread (q));
+ while (intq_empty (q))
+ wait (q, &q->not_empty);
+
+ byte = q->buf[q->tail];
+ q->tail = next (q->tail);
+ signal (q, &q->not_full);
+ return byte;
+}
+
+/* Adds BYTE to the end of Q.
+ Q must not be full if called from an interrupt handler.
+ Otherwise, if Q is full, first waits until a byte is removed.
+ Either Q must be locked or we must be in an interrupt
+ handler. */
+void
+intq_putc (struct intq *q, uint8_t byte)
+{
+ ASSERT (owned_by_current_thread (q));
+ while (intq_full (q))
+ wait (q, &q->not_full);
+
+ q->buf[q->head] = byte;
+ q->head = next (q->head);
+ signal (q, &q->not_empty);
+}
+\f
+/* Returns the position after POS within an intq. */
+static int
+next (int pos)
+{
+ return (pos + 1) % INTQ_BUFSIZE;
+}
+
+/* Returns true if Q is "owned by" the current thread; that is,
+ if Q is locked by the current thread or if we're in an
+ external interrupt handler. */
+static bool
+owned_by_current_thread (const struct intq *q)
+{
+ return (intr_context ()
+ || (lock_held_by_current_thread (&q->lock)
+ && intr_get_level () == INTR_OFF));
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+ member. Waits until the given condition is true. */
+static void
+wait (struct intq *q, struct thread **waiter)
+{
+ ASSERT (!intr_context ());
+ ASSERT (owned_by_current_thread (q));
+ ASSERT ((waiter == &q->not_empty && intq_empty (q))
+ || (waiter == &q->not_full && intq_full (q)));
+
+ *waiter = thread_current ();
+ thread_block ();
+}
+
+/* WAITER must be the address of Q's not_empty or not_full
+ member, and the associated condition must be true. If a
+ thread is waiting for the condition, wakes it up and resets
+ the waiting thread. */
+static void
+signal (struct intq *q, struct thread **waiter)
+{
+ ASSERT (owned_by_current_thread (q));
+ ASSERT ((waiter == &q->not_empty && !intq_empty (q))
+ || (waiter == &q->not_full && !intq_full (q)));
+
+ if (*waiter != NULL)
+ {
+ thread_unblock (*waiter);
+ *waiter = NULL;
+ }
+}
--- /dev/null
+#ifndef DEVICES_INTQ_H
+#define DEVICES_INTQ_H
+
+#include "threads/interrupt.h"
+#include "threads/synch.h"
+
+/* An "interrupt queue", a circular buffer shared between
+ kernel threads and external interrupt handlers.
+
+ A kernel thread that touches an interrupt queue must bracket
+ its accesses with calls to intq_lock() and intq_unlock().
+ These functions take a lock associated with the queue (which
+ locks out other kernel threads) and disable interrupts (which
+ locks out interrupt handlers).
+
+ An external interrupt handler that touches an interrupt queue
+ need not take any special precautions. Interrupts are
+ disabled in an external interrupt handler, so other code will
+ not interfere. The interrupt cannot occur during an update to
+ the interrupt queue by a kernel thread because kernel threads
+ disable interrupts while touching interrupt queues.
+
+ Incidentally, this has the structure of a "monitor". Normally
+ we'd use locks and condition variables from threads/synch.h to
+ implement a monitor. Unfortunately, those are intended only
+ to protect kernel threads from one another, not from interrupt
+ handlers. */
+
+/* Queue buffer size, in bytes. */
+#define INTQ_BUFSIZE 8
+
+/* A circular queue of bytes. */
+struct intq
+ {
+ /* Mutual exclusion. */
+ enum intr_level old_level; /* Excludes interrupt handlers. */
+ struct lock lock; /* Excludes kernel threads. */
+
+ /* Waiting threads. */
+ struct thread *not_full; /* Thread waiting for not-full condition. */
+ struct thread *not_empty; /* Thread waiting for not-empty condition. */
+
+ /* Queue. */
+ uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */
+ int head; /* New data is written here. */
+ int tail; /* Old data is read here. */
+ };
+
+void intq_init (struct intq *, const char *);
+void intq_lock (struct intq *);
+void intq_unlock (struct intq *);
+bool intq_empty (const struct intq *);
+bool intq_full (const struct intq *);
+uint8_t intq_getc (struct intq *);
+void intq_putc (struct intq *, uint8_t);
+
+#endif /* devices/intq.h */
#include "devices/serial.h"
#include <debug.h>
#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"
-#define QUEUE_BUFSIZE 8
+/* Transmission mode. */
+static enum { UNINIT, POLL, QUEUE } mode;
-struct queue
- {
- enum intr_level old_level;
- struct lock lock;
-
- struct thread *not_full, *not_empty;
- uint8_t buf[QUEUE_BUFSIZE];
- int head, tail;
- };
-
-static struct queue recv_queue, xmit_queue;
-static bool polled_io;
+/* Data to be transmitted. */
+static struct intq txq;
static void set_serial (int bps, int bits, enum parity_type parity, int stop);
-static void update_ier (void);
+static void write_ier (void);
static intr_handler_func serial_interrupt;
-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);
-
/* Initializes the serial port device for polling mode.
Polling mode busy-waits for the serial port to become free
before writing to it. It's slow, but until interrupts have
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. */
+ mode = POLL;
}
/* Initializes the serial port device for queued interrupt-driven
void
serial_init_queue (void)
{
- ASSERT (polled_io);
- polled_io = false;
- queue_init (&recv_queue, "serial recv");
- queue_init (&xmit_queue, "serial xmit");
+ ASSERT (mode == POLL);
+ intq_init (&txq, "serial xmit");
intr_register (0x20 + 4, 0, INTR_OFF, 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);
+ if (mode == POLL || intr_context ())
+ {
+ /* Poll the serial port until it's ready for a byte, and
+ then transmit. */
+ while ((inb (LSR_REG) & LSR_THRE) == 0)
+ continue;
+ outb (THR_REG, 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);
-
- queue_putc (&xmit_queue, byte);
- update_ier ();
-
- queue_unlock (&xmit_queue);
-}
-
-/* Reads and returns a byte from the serial port. */
-uint8_t
-serial_getc (void)
-{
- uint8_t byte;
- queue_lock (&recv_queue);
-
- /* Read a byte. */
- byte = queue_getc (&recv_queue);
- update_ier ();
-
- queue_unlock (&recv_queue);
- return byte;
+ {
+ /* Lock the queue, add a byte, and update the interrupt
+ enable register. */
+ intq_lock (&txq);
+ intq_putc (&txq, byte);
+ write_ier ();
+ intq_unlock (&txq);
+ }
}
-/* 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, BITS bits
+ per byte, the given PARITY, and STOP stop bits. */
static void
set_serial (int bps, int bits, enum parity_type parity, int stop)
{
}
/* 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);
}
+/* 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
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 ();
-}
-\f
-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)
-{
- 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 ();
-}
-
-static void
-signal (struct queue *q, struct thread **waiter)
-{
- 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 ();
}