Based on code from Anthony Romano <chz@vt.edu> but with extensive changes.
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
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.
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.
--- /dev/null
+#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);
+}
--- /dev/null
+#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 */
--- /dev/null
+#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 ();
+ }
+}
--- /dev/null
+#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 */
#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"
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");
}
#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"
if (cx >= COL_CNT)
newline ();
break;
+
+ case '\a':
+ intr_set_level (old_level);
+ speaker_beep ();
+ intr_disable ();
+ break;
default:
fb[cy][cx][0] = c;