From f1f2dc8de9e336d83383692d4478bb14a3dafc11 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 9 Nov 2008 20:02:19 -0800 Subject: [PATCH] Add PC speaker driver and connect it to '\a' in the VGA console. Based on code from Anthony Romano but with extensive changes. --- doc/threads.texi | 10 ++++++ src/Makefile.build | 4 ++- src/devices/pit.c | 83 +++++++++++++++++++++++++++++++++++++++++++ src/devices/pit.h | 8 +++++ src/devices/speaker.c | 68 +++++++++++++++++++++++++++++++++++ src/devices/speaker.h | 8 +++++ src/devices/timer.c | 16 +++------ src/devices/vga.c | 7 ++++ 8 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 src/devices/pit.c create mode 100644 src/devices/pit.h create mode 100644 src/devices/speaker.c create mode 100644 src/devices/speaker.h diff --git a/doc/threads.texi b/doc/threads.texi index b13b545..d90b438 100644 --- a/doc/threads.texi +++ b/doc/threads.texi @@ -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 diff --git a/src/Makefile.build b/src/Makefile.build index ceb3412..b733205 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -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 index 0000000..bfb1889 --- /dev/null +++ b/src/devices/pit.c @@ -0,0 +1,83 @@ +#include "devices/pit.h" +#include +#include +#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 index 0000000..dff36ae --- /dev/null +++ b/src/devices/pit.h @@ -0,0 +1,8 @@ +#ifndef DEVICES_PIT_H +#define DEVICES_PIT_H + +#include + +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 index 0000000..5052005 --- /dev/null +++ b/src/devices/speaker.c @@ -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 index 0000000..98cef7b --- /dev/null +++ b/src/devices/speaker.h @@ -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 */ diff --git a/src/devices/timer.c b/src/devices/timer.c index 70234a8..c97667f 100644 --- a/src/devices/timer.c +++ b/src/devices/timer.c @@ -3,8 +3,8 @@ #include #include #include +#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"); } diff --git a/src/devices/vga.c b/src/devices/vga.c index 8255747..f421b61 100644 --- a/src/devices/vga.c +++ b/src/devices/vga.c @@ -3,6 +3,7 @@ #include #include #include +#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; -- 2.30.2