From: Ben Pfaff Date: Tue, 7 Sep 2004 23:49:03 +0000 (+0000) Subject: Move serial interrupt queue into new file intq.c. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b2a1e970fa78d8b4c31239ff2ac9ef2b4bab09a7;p=pintos-anon Move serial interrupt queue into new file intq.c. Remove serial receive code. --- diff --git a/src/Makefile.build b/src/Makefile.build index 3886b5d..e55ce56 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -32,6 +32,7 @@ devices_SRC += devices/kbd.c # Keyboard device. devices_SRC += devices/vga.c # Video device. devices_SRC += devices/serial.c # Serial port device. devices_SRC += devices/disk.c # IDE disk device. +devices_SRC += devices/intq.c # Interrupt queue. # Library code shared between kernel and user programs. lib_SRC = lib/debug.c # Debug helpers. diff --git a/src/devices/intq.c b/src/devices/intq.c new file mode 100644 index 0000000..7f73991 --- /dev/null +++ b/src/devices/intq.c @@ -0,0 +1,144 @@ +#include "devices/intq.h" +#include +#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); +} + +/* 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; + } +} diff --git a/src/devices/intq.h b/src/devices/intq.h new file mode 100644 index 0000000..5c278fa --- /dev/null +++ b/src/devices/intq.h @@ -0,0 +1,57 @@ +#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 */ diff --git a/src/devices/serial.c b/src/devices/serial.c index a27c50a..64ce90f 100644 --- a/src/devices/serial.c +++ b/src/devices/serial.c @@ -1,39 +1,23 @@ #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" -#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 @@ -41,12 +25,12 @@ 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. */ + mode = POLL; } /* Initializes the serial port device for queued interrupt-driven @@ -55,62 +39,37 @@ 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"); + 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) { @@ -129,148 +88,23 @@ 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 (); -} - -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 (); } diff --git a/src/devices/serial.h b/src/devices/serial.h index 3ef124b..3500ffa 100644 --- a/src/devices/serial.h +++ b/src/devices/serial.h @@ -3,8 +3,8 @@ #include -void serial_init (int phase); +void serial_init_poll (void); +void serial_init_queue (void); void serial_putc (uint8_t); -uint8_t serial_getc (void); #endif /* devices/serial.h */ diff --git a/src/threads/init.c b/src/threads/init.c index 2684d17..35ce92a 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -55,7 +55,7 @@ main (void) /* Needed by printf(), so initialize them very early. */ ram_init (); vga_init (); - serial_init (1); + serial_init_poll (); /* Greet user. */ printf ("Booting cnachos86 with %'d kB RAM...\n", ram_pages * 4); @@ -87,7 +87,7 @@ main (void) /* Start thread scheduler and enable interrupts. */ thread_start (); - serial_init (2); + serial_init_queue (); #ifdef FILESYS /* Initialize filesystem. */ @@ -98,9 +98,6 @@ main (void) printf ("Boot complete.\n"); - for (;;) - putchar (serial_getc ()); - #ifdef USERPROG /* Run a user program. */ if (initial_program != NULL)