Add PC speaker driver and connect it to '\a' in the VGA console.
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 10 Nov 2008 04:02:19 +0000 (20:02 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 10 Nov 2008 04:02:19 +0000 (20:02 -0800)
Based on code from Anthony Romano <chz@vt.edu> but with extensive changes.

doc/threads.texi
src/Makefile.build
src/devices/pit.c [new file with mode: 0644]
src/devices/pit.h [new file with mode: 0644]
src/devices/speaker.c [new file with mode: 0644]
src/devices/speaker.h [new file with mode: 0644]
src/devices/timer.c
src/devices/vga.c

index b13b5450ff00802e06fa346894a6955ece891f4e..d90b43895396a0ec81c3ff9c4ae91515572522ac 100644 (file)
@@ -246,6 +246,16 @@ and serial drivers.
 Real-time clock driver, to enable the kernel to determine the current
 date and time.  By default, this is only used by @file{thread/init.c}
 to choose an initial seed for the random number generator.
+
+@item speaker.c
+@itemx speaker.h
+Driver that can produce tones on the PC speaker.
+
+@item pit.c
+@itemx pit.h
+Code to configure the 8254 Programmable Interrupt Timer.  This code is
+used by both @file{devices/timer.c} and @file{devices/speaker.c}
+because each device uses one of the PIT's output channel.
 @end table
 
 @node lib files
index ceb34121b6aeb5557097ad941f8119156c8ad323..b73320579ecc342f0b28b0f8bc78bb3ea513de3b 100644 (file)
@@ -23,7 +23,8 @@ threads_SRC += threads/malloc.c               # Subpage allocator.
 threads_SRC += threads/start.S         # Startup code.
 
 # Device driver code.
-devices_SRC  = devices/timer.c         # Timer device.
+devices_SRC  = devices/pit.c           # Programmable interrupt timer chip.
+devices_SRC += devices/timer.c         # Periodic timer device.
 devices_SRC += devices/kbd.c           # Keyboard device.
 devices_SRC += devices/vga.c           # Video device.
 devices_SRC += devices/serial.c                # Serial port device.
@@ -32,6 +33,7 @@ devices_SRC += devices/input.c                # Serial and keyboard input.
 devices_SRC += devices/intq.c          # Interrupt queue.
 devices_SRC += devices/rtc.c           # Real-time clock.
 devices_SRC += devices/shutdown.c      # Reboot and power off.
+devices_SRC += devices/speaker.c       # PC speaker.
 
 # Library code shared between kernel and user programs.
 lib_SRC  = lib/debug.c                 # Debug helpers.
diff --git a/src/devices/pit.c b/src/devices/pit.c
new file mode 100644 (file)
index 0000000..bfb1889
--- /dev/null
@@ -0,0 +1,83 @@
+#include "devices/pit.h"
+#include <debug.h>
+#include <stdint.h>
+#include "threads/interrupt.h"
+#include "threads/io.h"
+
+/* Interface to 8254 Programmable Interrupt Timer (PIT).
+   Refer to [8254] for details. */
+
+/* 8254 registers. */
+#define PIT_PORT_CONTROL          0x43                /* Control port. */
+#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL))  /* Counter port. */
+
+/* PIT cycles per second. */
+#define PIT_HZ 1193180
+
+/* Configure the given CHANNEL in the PIT.  In a PC, the PIT's
+   three output channels are hooked up like this:
+
+     - Channel 0 is connected to interrupt line 0, so that it can
+       be used as a periodic timer interrupt, as implemented in
+       Pintos in devices/timer.c.
+
+     - Channel 1 is used for dynamic RAM refresh (in older PCs).
+       No good can come of messing with this.
+
+     - Channel 2 is connected to the PC speaker, so that it can
+       be used to play a tone, as implemented in Pintos in
+       devices/speaker.c.
+
+   MODE specifies the form of output:
+
+     - Mode 2 is a periodic pulse: the channel's output is 1 for
+       most of the period, but drops to 0 briefly toward the end
+       of the period.  This is useful for hooking up to an
+       interrupt controller to generate a periodic interrupt.
+
+     - Mode 3 is a square wave: for the first half of the period
+       it is 1, for the second half it is 0.  This is useful for
+       generating a tone on a speaker.
+
+     - Other modes are less useful.
+
+   FREQUENCY is the number of periods per second, in Hz. */
+void
+pit_configure_channel (int channel, int mode, int frequency)
+{
+  uint16_t count;
+  enum intr_level old_level;
+
+  ASSERT (channel == 0 || channel == 2);
+  ASSERT (mode == 2 || mode == 3);
+
+  /* Convert FREQUENCY to a PIT counter value.  The PIT has a
+     clock that runs at PIT_HZ cycles per second.  We must
+     translate FREQUENCY into a number of these cycles. */
+  if (frequency < 19)
+    {
+      /* Frequency is too low: the quotient would overflow the
+         16-bit counter.  Force it to 0, which the PIT treats as
+         65536, the highest possible count.  This yields a 18.2
+         Hz timer, approximately. */
+      count = 0;
+    }
+  else if (frequency > PIT_HZ)
+    {
+      /* Frequency is too high: the quotient would underflow to
+         0, which the PIT would interpret as 65536.  A count of 1
+         is illegal in mode 2, so we force it to 2, which yields
+         a 596.590 kHz timer, approximately.  (This timer rate is
+         probably too fast to be useful anyhow.) */
+      count = 2;
+    }
+  else
+    count = (PIT_HZ + frequency / 2) / frequency;
+
+  /* Configure the PIT mode and load its counters. */
+  old_level = intr_disable ();
+  outb (PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1));
+  outb (PIT_PORT_COUNTER (channel), count);
+  outb (PIT_PORT_COUNTER (channel), count >> 8);
+  intr_set_level (old_level);
+}
diff --git a/src/devices/pit.h b/src/devices/pit.h
new file mode 100644 (file)
index 0000000..dff36ae
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DEVICES_PIT_H
+#define DEVICES_PIT_H
+
+#include <stdint.h>
+
+void pit_configure_channel (int channel, int mode, int frequency);
+
+#endif /* devices/pit.h */
diff --git a/src/devices/speaker.c b/src/devices/speaker.c
new file mode 100644 (file)
index 0000000..5052005
--- /dev/null
@@ -0,0 +1,68 @@
+#include "devices/speaker.h"
+#include "devices/pit.h"
+#include "threads/io.h"
+#include "threads/interrupt.h"
+#include "devices/timer.h"
+
+/* Speaker port enable I/O register. */
+#define SPEAKER_PORT_GATE      0x61
+
+/* Speaker port enable bits. */
+#define SPEAKER_GATE_ENABLE    0x03
+
+/* Sets the PC speaker to emit a tone at the given FREQUENCY, in
+   Hz. */
+void
+speaker_on (int frequency)
+{
+  if (frequency >= 20 && frequency <= 20000)
+    {
+      /* Set the timer channel that's connected to the speaker to
+         output a square wave at the given FREQUENCY, then
+         connect the timer channel output to the speaker. */
+      enum intr_level old_level = intr_disable ();
+      pit_configure_channel (2, 3, frequency);
+      outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE);
+      intr_set_level (old_level);
+    }
+  else
+    {
+      /* FREQUENCY is outside the range of normal human hearing.
+         Just turn off the speaker. */
+      speaker_off ();
+    }
+}
+
+/* Turn off the PC speaker, by disconnecting the timer channel's
+   output from the speaker. */
+void
+speaker_off (void)
+{
+  enum intr_level old_level = intr_disable ();
+  outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE);
+  intr_set_level (old_level);
+}
+
+/* Briefly beep the PC speaker. */
+void
+speaker_beep (void)
+{
+  /* Only attempt to beep the speaker if interrupts are enabled,
+     because we don't want to freeze the machine during the beep.
+     We could add a hook to the timer interrupt to avoid that
+     problem, but then we'd risk failing to ever stop the beep if
+     Pintos crashes for some unrelated reason.  There's nothing
+     more annoying than a machine whose beeping you can't stop
+     without a power cycle.
+
+     We can't just enable interrupts while we sleep.  For one
+     thing, we get called (indirectly) from printf, which should
+     always work, even during boot before we're ready to enable
+     interrupts. */
+  if (intr_get_level () == INTR_ON)
+    {
+      speaker_on (440);
+      timer_msleep (250);
+      speaker_off ();
+    }
+}
diff --git a/src/devices/speaker.h b/src/devices/speaker.h
new file mode 100644 (file)
index 0000000..98cef7b
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DEVICES_SPEAKER_H
+#define DEVICES_SPEAKER_H
+
+void speaker_on (int frequency);
+void speaker_off (void);
+void speaker_beep (void);
+
+#endif /* devices/speaker.h */
index 70234a808844e425d8309f6cd04c09ce11ad230e..c97667f56bfcd5d9bd70862c7ff3ca2047f8f706 100644 (file)
@@ -3,8 +3,8 @@
 #include <inttypes.h>
 #include <round.h>
 #include <stdio.h>
+#include "devices/pit.h"
 #include "threads/interrupt.h"
-#include "threads/io.h"
 #include "threads/synch.h"
 #include "threads/thread.h"
   
@@ -30,20 +30,12 @@ static void busy_wait (int64_t loops);
 static void real_time_sleep (int64_t num, int32_t denom);
 static void real_time_delay (int64_t num, int32_t denom);
 
-/* Sets up the 8254 Programmable Interval Timer (PIT) to
-   interrupt PIT_FREQ times per second, and registers the
-   corresponding interrupt. */
+/* Sets up the timer to interrupt TIMER_FREQ times per second,
+   and registers the corresponding interrupt. */
 void
 timer_init (void) 
 {
-  /* 8254 input frequency divided by TIMER_FREQ, rounded to
-     nearest. */
-  uint16_t count = (1193180 + TIMER_FREQ / 2) / TIMER_FREQ;
-
-  outb (0x43, 0x34);    /* CW: counter 0, LSB then MSB, mode 2, binary. */
-  outb (0x40, count & 0xff);
-  outb (0x40, count >> 8);
-
+  pit_configure_channel (0, 2, TIMER_FREQ);
   intr_register_ext (0x20, timer_interrupt, "8254 Timer");
 }
 
index 8255747244e641d599d686fadecb167a9fc6b4f7..f421b617bd3706858d2536eb21866531b1fd9b85 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <string.h>
+#include "devices/speaker.h"
 #include "threads/io.h"
 #include "threads/interrupt.h"
 #include "threads/vaddr.h"
@@ -80,6 +81,12 @@ vga_putc (int c)
       if (cx >= COL_CNT)
         newline ();
       break;
+
+    case '\a':
+      intr_set_level (old_level);
+      speaker_beep ();
+      intr_disable ();
+      break;
       
     default:
       fb[cy][cx][0] = c;