Carnegie Mellon
SCS logo
Computer Science Department

15-410 Project 4: SoundBlaster 16


This semester, your Project 4 task is something musical. To commemmorate Professor Dannenberg's tenure on 15-410 staff, we've decided to assign a simple sound driver for the Creative Sound Blaster 16 sound card. You will implement a driver capable of playing back audio in a specific format and the system calls snd_buffer() and snd_play() to interface to it from userspace.

Project 4 is due Wednesday, December 3rd, at 23:59. When planning your work, keep in mind that the book report and the final homework assignment will be due on the last day of classes.

Note that P4 grades will probably not be returned before the final exam; in the other direction, the exam will not test you on P4 material as such (for that reason and because not all groups will work on P4).

What is sound, anyway?

To begin with, it seems reasonable to explain how sound works from a programmer's perspective. If you are already familiar with sound playback on a computer, feel free to skip this section.

Sound is formed by rapid changes in pressure that travel as a wave through the air (or some other medium). Our ears pick up this waveform, and through a complex process that is not fully understood, our hearing apparatus transforms the acoustic signal into a neural representation so that we can perceive sound: tones, impulses, noise, speech, etc. Audio electronics and computers represent sound vibrations as a function of time that can be amplified and converted into sound by speakers or headphones.

Digital computers cannot represent arbitrary continuous waveforms, but we can approximate them by chopping them up into periodic samples in time. To provide input for both of our ears, we sample two waveforms -- a left one, and a right one. Each of these waveforms we refer to as a channel, and unsurprisingly, each of the samples at any given time from one of the channels is referred to as a sample. These samples are usually stored in pairs -- one for each channel. A pair of samples represent a moment in time of the stereo signal and is called a frame. The frequency of these frames is called the sampling rate. Thus a stereo signal will be stored in memory as a sequence of samples: frame 0 left, frame 0 right, frame 1 left, frame 1 right, etc.

Samples can be stored in many formats. The most common uncompressed format, known as PCM, encodes the amplitude of the audio wave linearly as an integer. Usually, samples are either 8 bits or 16 bits wide (where more bits result in less noise). They are usually stored in either unsigned magnitude format (where UINT_MAX/2 is a zero magnitude output, 0 is a maximally negative output, and UINT_MAX is a maximally positive output) or in two's complement signed magnitude format (where 0 is a zero magnitude output, -INT_MAX is a maximally negative output, and INT_MAX is a maximally positive output). You need not know the details of how data are stored precisely for this assignment; we will not ask you to do any manipulation on the data, other than understanding how large a frame is and making sure that you're not passing partial frames about.

A sound design

In this assignment, we'll constrain you to one specific format of sound data. You will be working with 2-channels (stereo) of 16-bit, signed, PCM audio that has a sampling rate of 44100Hz. Due to the limited nature of the Sound Blaster 16's programming interface and the limited scope of this project, you are authorized to have these options set at compile time, not at run time, with the proviso that they must be changeable by reasonable measures from a programmer. (If you need a refresher, see the #define lecture.)

To get sound data from user space into the kernel, you will implement the following system calls:

  • int snd_buffer(short *buf, int samples)

    Attempts to add the number of samples (not the number of frames!) specified by samples from the buffer buf into the system-wide buffer of samples ready to be played. snd_buffer is specified to return upon copying all of the samples into the system's buffer; if there is insufficient room to copy the samples, then snd_buffer blocks the calling process until sufficient space is available for all of the samples. Once snd_buffer returns, the kernel is no longer using buf, and buf is free to be reused by the user process for whatever purpose; the samples formerly in buf are guaranteed to have been copied contiguously into the system's buffer.

    Rephrased, snd_buffer is an atomic operation; multiple tasks calling snd_buffer at the same time must not have "parts" of their samples mixed in any fashion. (Consider what would happen if they did by drawing the output waveform on paper -- a disastrous noise would emanate from the user's speakers...)

    The system call returns an error code less than zero if for some reason the samples could not be buffered. This may include the kernel running low on some critical resource necessary for the call to succeed, the kernel's buffer being too small to accommodate samples samples at the same time, or buf residing in invalid memory. The snd_buffer specification does not include specific values for the various error conditions, which may vary from one kernel implementation to another.

  • int snd_play()

    Starts the sound hardware playing back from the kernel's buffer. For streaming audio (i.e., samples that will be buffered one after another), this call is recommended after every snd_buffer call; if the system runs out of buffered audio to send to the sound card, then sound will not be played again until another snd_play call is issued. Sound will not begin playing immediately after snd_buffer calls until snd_play calls are issued, unless sound was already playing, in which case sound will continue playing into the newly buffered data. snd_play returns 0 if the card was already playing, 1 if the card has successfully started playing, or an error code less than zero if for some reason the card could not be started, including but not limited to the card not existing.

The buffer that your kernel maintains with snd_buffer() need not (and in fact, probably should not) be the same as the buffer that the sound card directly transfers samples from; see below for more on that. snd_buffer's buffer should hold no less than 32768 samples; your buffer may hold more (or many more!) than that at your discretion.

Talking to the Hardware

The Sound Blaster 16, or SB16, is a relatively easy-to-program sound card that was very common for its time; it was effectively the de facto standard for sound cards. The SB16 uses the ISA bus (for "Industry Standard Architecture" -- a bus specification originated in the 1980s). Although the SB16 is now obsolete, ISA bus cards are much simpler to deal with than the PCI bus and PCI configuration. The SB16, like many parts of PC architecture, is the result of many revisions and evolutions. As such, the interface contains plenty of bits of legacy; luckily, you need not worry about most of it.

There are two major mechanisms by which you will communicate with the Sound Blaster -- the DMA channel and the command interface.

Much of the following data was gleaned from the excellent Sound Blaster 16 Programming Document by Ethan Brodsky.

Sound Blaster Resources

Most of your communications with the Sound Blaster, with the exception of the actual sound data, will go over the command interface. The command interface uses up a specific set of the system's resources that may be at a few set locations.

The Sound Blaster, like most ISA cards, is programmed using the PC's I/O ports (similar to the PIC, the keyboard, and the timer). It uses 16 contiguous ports starting at 0x220, 0x240, 0x260, or 0x280. The Sound Blaster may be at any of these locations, depending on what other cards are in the system, so your driver must probe the Sound Blaster to determine where it is. A good method to determine whether a Sound Blaster is present is by attempting to write a reset sequence to the card, and seeing if it responds in a reasonable amount of time (say, 10 milliseconds) -- see the section on Resetting the Card further down for more information on that. For linguistic ease, the first register used by the Sound Blaster will be referred to as the base address; and to refer to a specific offset from the base address, we'll say (for example) 0x2xA to refer to the base address plus hexidecimal A.

The Sound Blaster also is assigned a certain interrupt number. To determine which interrupt number your driver should attempt to handle, you must ask the Sound Blaster which interrupt it has been assigned. To do so, read mixer register number 0x80 (see the section Accessing Mixer Registers further down) and decode it as follows:

  • If bit 0 is set, the Sound Blaster is on IRQ2.
  • If bit 1 is set, the Sound Blaster is on IRQ5.
  • If bit 2 is set, the Sound Blaster is on IRQ7.
  • If bit 3 is set, the Sound Blaster is on IRQ10. Note that to acknowledge interrupts greater than 7, you need to both acknowledge the secondary PIC and the primary PIC; i.e., in addition to writing INT_CTL_DONE to port INT_CTL_PORT, you need to write the same value to port SLAVE_ICW.

Finally, the Sound Blaster is assigned two different DMA channels (see the section later on about Playing Audio for more about those). For this project, you only need to know about the "High DMA" channel; but for the sake of completeness, both the "Low DMA" and the "High DMA" channels will be decoded here. To read the DMA channel configuration out of the card, read mixer register number 0x81, and decode it as follows:

  • If bit 0 is set, the Sound Blaster's low DMA channel is 0.
  • If bit 1 is set, the Sound Blaster's low DMA channel is 1.
  • If bit 2 is set, the Sound Blaster's low DMA channel is 3.
  • If bit 5 is set, the Sound Blaster's high DMA channel is 5.
  • If bit 6 is set, the Sound Blaster's high DMA channel is 6.
  • If bit 7 is set, the Sound Blaster's high DMA channel is 7.

These conclude the resources used by the Sound Blaster 16 card.

Resetting the Card

The first thing that your driver must do before attempting to access the card is reset the card. To do so, write a 1 to the reset port (0x2x6), wait at least 3 microseconds (the function iodelay() takes 1/8 microsecond), and then write a 0 to the reset port. If the card has been successfully reset, then it will place the value 0xAA into its read queue in some small period of time (1000 attempts to check the read queue will usually do the trick). See the section on The Command Queues further down for information on reading the read queue.

It is important that your driver cope gracefully with the potential that the Sound Blaster does not exist on the system, and that it also cope gracefully with the potential that the Sound Blaster does not exist at the first address that your driver tries; in particular, looping infinitely waiting for the card to return bytes in the read queue might be a bad idea, at least for the first reset read.

After you have reset the card, it is a good idea (read: mandatory) for your driver to verify that the card's version is a version that you can handle. To do so, write the command 0xE1 to the card's write/command queue, and read two bytes out of the read queue. The first byte returned is the major version number of the card, and the second byte returns is the minor version of the card. This document has commands specific to the Sound Blaster 16, which is major version number 4 or higher; so if a card returns a major version number lower than 4, you should just proceed on as if the card didn't come out of reset.

The Command Queues

To communicate with the Sound Blaster 16 for most commands, your driver needs to read from the card's read queue, and write to the card's write queue. To ask the Sound Blaster 16 to perform a command, your driver will write the byte associated with that command (and optionally parameters for the command) into the Sound Blaster's write queue; and to read any response that might come back from the command, your driver will read bytes from the Sound Blaster's read queue.

Writing to the command/write queue is a two-step procedure. Your driver must first verify that the card has space in the write queue and is ready to accept your command or datum; and after that, it can actually write the command or datum itself. To determine if there is space in the queue, poll on the write status register (0x2xC) until bit 7 is unset; once bit 7 is unset, there exist slots in the queue that you can write to. To actually write into the queue, place your data in the write register itself (which is also at 0x2xC).

Reading from the read queue is an analogous two-step procedure, but with the direction of the final access changed, and with the addresses changed. Polling is similar to the write procedure, but the read status register is 0x2xE; and you must instead wait until bit 7 is set before you are permitted to read. To read data after the status register shows that data is available, take data from the read register, 0x2xA. Note that reading is not a pure function; after you have read data, it is not available again to read later!

Accessing Mixer Registers

Mixer registers on the Sound Blaster 16 are accessed through a similar interface as VGA registers are. In particular, the registers are windowed; first specify the number of the register that you access by writing to the Mixer Address Register (0x2x4), and then perform the desired access on the Mixer Data Register (0x2x5). Refer to the lecture notes accessing the video card (VGA registers) if you need a refresher on windowed registers.

You need not worry about most of the mixer registers on the Sound Blaster other than those that we have already described (0x80 and 0x81). If you are very enthusiastic, more information on mixer registers will be made available on request.

Playing Audio

All of the above is well and good, but until now we've ignored the main purpose of a sound card -- to play sound! The main method of controlling when and how the Sound Blaster 16 will play audio for you is through the command queue, as described previously. There are many different forms of the needed commands to set up the sound device; since this driver will support only a limited subset of the capabilities of the Sound Blaster 16, this document will only describe a few.

The "game plan" for playing back audio will have three major components. When your driver initializes, it must set up the DMA controller so that the Sound Blaster can transfer data when it needs it. This DMA setup is described in the section Programming the DMA Controller. After it sets up the DMA controller, it should send the play command to the card, then immediately pause it until it is ready to send data. The play command should be sent only once, because you wish to keep the DMA controller's state in sync with the Sound Blaster's. Finally, when you are ready to actually play back audio, you will send the continue and pause commands as needed to actually control the card.

With the overview out of the way, we can get to the business of playing sound. Before your driver can begin playing back audio, it needs to set the playback sample rate on the card. The command to set the sample rate is 0x41 0xMM 0xLL; i.e., first send the command 0x41, then send the most significant byte of the sample rate, then send the least significant byte of the sample rate, waiting for queue space as usual between each sent command byte. This only needs to be done once; the card remembers the sample rate between play requests. It is considered inadvisable to change the output sample rate while the card is playing back audio.

After your driver has set the sample rate, it can tell the card to begin playing back audio. The command to begin audio playback is 0xB6 0x30 0xLL 0xMM (note that the silly hardware designers changed the endianness on you this time); in this case, 0xMM and 0xLL refer to the most and least significant bytes of the number of samples minus one (not the number of bytes, or the number of frames) that the Sound Blaster will play before giving you an interrupt. (Note that this number need not be the size of the buffer; see later on for more information on that.) Roughly, this command decodes to "Begin 16-bit, auto-initialized, signed, stereo playback". The "auto-initialized" bit is in contrast to a single-shot playback; once the card has played back the specified number of samples, the card will give an interrupt and continue reading and playing back more samples.

Since you don't have any data to feed the card when your driver starts up, you will probably want to immediately send the pause command. The 16-bit pause command is 0xD5. At some point, you will have data to give the card, and to start the card playing again without losing its place in the buffer, you will want to send the continue command, 0xD6. As life in your kernel goes on, your driver will surely need to stop the card again; worry not, for your driver is permitted to pause and continue playback on the card as much and as often as it needs to. If it becomes the case that you'd like to play up until the end of this block (i.e., terminate playback exactly on the next interrupt), send the card the stop command, 0xD9.

These conclude the commands that your driver will need to send to the sound card's queue. There are many more that are documented in the page linked above, and as well in the Creative Developer's Kit, which is available upon request. (Sadly, there are even more that are undocumented even in the Developer's Kit, including a magic command 0xE3 that enqueues the text "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992." in the read queue...)

Programming the DMA Controller

Now that you know how to program the Sound Blaster to play audio, you may wonder where the audio data actually comes from. Since the Sound Blaster is an ISA card, it is unable to transfer data directly from host memory itself (such behavior is limited to bus-mastering interfaces, such as PCI), and hence it relies on yet another hardware abstraction called the DMA controller to do the data transfer on its behalf. The DMA controller chooses which device to transfer data to by using DMA channels; typically, each device on an ISA system that needs to directly transfer from or to memory will use up one or two of these channels.

This specification, for the purposes of clarity, will give an extremely simplified description of how to set up the DMA controller. Although we will attempt to document limitations here, the DMA controller is much more powerful than the description that we give here, and capable of many more modes of operation. Using this description for anything other than the specified purpose is probably inadvisable. And while we're giving warnings, it should be noted that no modern device uses the DMA controller. The DMA controller/channel model is an outdated means of accessing host memory; almost all modern bus interfaces are capable of bus-mastering, which means that the procedure for programming them will be between somewhat and substantially different. The DMA controller is by far the weirdest part of this hardware to program, and is a bit unforgiving with its constraints, so be careful.

Now that we have that out of the way, on to programming the DMA controller. Before you program the DMA controller, you must know three things -- the physical address of your buffer, the size of your buffer, and the channel number that you wish to program. The channel number can be obtained from the above-specified method; you want to program the high DMA channel. The buffer must not span a 64-kilobyte boundary, and it must not be larger than 32 kilowords (i.e., 64 kilobytes).

The addresses that your driver must write to depend on the channel number that you wish to program. For convenience, they are listed in a table at the end of this section. To program the DMA controller, execute the following steps in order:

  1. Disable the DMA channels that you're about to program by writing the mask register with the value 4 + (DMACH%4), where DMACH is the DMA channel that you're programming.
  2. Set the DMA controller to start at the beginning of the buffer by writing any value to the clear port. (The next section gives a mapping of port names to port numbers.)
  3. Tell the DMA controller the mode that you're about to program by writing the mode port with the value 0x58 + (DMACH%4). This roughly decodes to "auto-initialized playback"; for more on this, see Ethan Brodsky's excellent document linked above. The auto-initialized part of this setting refers to the fact that when the DMA controller hits the end of the buffer, it will re-initialize itself -- i.e., restart -- with the same settings that you initially gave it.
  4. Now program the buffer address into the DMA controller. This will be done in four steps; the first two are reasonable enough, and the last two comprise a hack and a hack on a hack, respectively.
    • For the DMA controller itself, you'll need to take the physical address of your buffer and right shift it by 1 bit to get the word address of your buffer. Write the lower two bytes of the word address of your buffer into the address port for your channel on the DMA controller, starting with the least significant byte. (This is the 'reasonable enough' step).
    • Now here comes the hack -- the 8237 DMA controller that the PC uses didn't support more than 16-bit addressing, so each DMA channel was given a "page" port that would specify the upper bits on the bus. This extended the DMA controller's addressing capabilities to 24 bits. But, the page port works in terms of the physical address, not the word address of your buffer; so write bits 16 through 23 into the low page port for your channel.
    • Sadly, 24 bits just wasn't good enough, either. (Go figure.) To address those final 8 bits that were left out, another hack was added -- yet another page port that specified the high byte. This works just like the low page port, but it specifies the high 8 bits. Write the high bits (bits 24 through 31) into the high page port for your channel.

    For those of you confused by the bitfields specified above, we've provided a table of which bits go where.

    31302928272625 2423222120191817 16151413121110 9876533 210
    Address write #1
    Address write #2
    Low page
    High page

    (For those of you paying close attention, it is strange but true that bit sixteen has two sources. It might be wise for you to program the same value each time.)

  5. Program how large your buffer is into the DMA controller. (Remember, this need not be the same size that you sent to the Sound Blaster. We'll talk about that in a moment.) Since the high DMA controller deals with 16 bit words, write the number of words that you'd like the DMA controller to send before it wraps to the beginning of the buffer minus one into the count port for your channel. This value is two bytes -- send first the low byte, then the high byte.
  6. If everything's gone well, the DMA channel is now programmed with what data and how much data to transfer to the sound card. To enable the DMA channel to begin transferring data to the sound card when the sound card requests it, unmask the channel. Do so by writing the value DMACH%4 to the mask port. Your driver should do this immediately after programming the DMA controller, as opposed to waiting for playback to start. This concludes the DMA controller initialization procedure.

DMA Controller Addresses

All of these addresses represent single-byte I/O ports, to be accessed with the inb/outb calls.

Low Page50x8B
High Page50x48B

Acknowledging Interrupts

The Sound Blaster 16 has some specific mechanisms for acknowledging interrupts. In addition to acknowledging the interrupt with the PIC, you must also read from the interrupt acknowledge port (0x2xF). The data that you read back is unimportant; the only requirement is that you read from it. Be sure to acknowledge both PICs (both the master and the slave) if the sound blaster is on an IRQ higher than 7; the procedure for acknowledging the slave PIC is identical to acknowledging the master PIC, but for the fact that you must write the datum to port 0xA0 instead of INT_CTL_REG.

Managing the Buffer

In theory, we have given you all the information you need to drive the Sound Blaster 16 at this point. However, buffer management is an issue which will require some creativity.

A modern sound-device interface, such AC '97, incorporates a DMA engine which works from a queue of buffers. On such a device, as each buffer is exhausted, it is marked as available for the host to refill; the host can also flag each buffer as generating an interrupt when it is exhausted. This architecture enables stall-free playback: while the sound interface is turning some buffers into sound, the host is filling other buffers. The author of the device driver on the host can tune the frequency with which the DMA engine issues interrupts. Frequent interrupts will reduce the likelihood that the sound card runs out of data while increasing the cost of playing sound. If the device driver author knows that the kernel is very preemptible, i.e., there is a small bound on the delay between the DMA engine issuing an interrupt and the driver code running, the driver can space the interrupts further apart or can use a shallower buffer queue (thus enabling interactive applications such as VoIP).

Unfortunately, the ISA DMA engine doesn't support arbitrary buffer linking. In particular, a given DMA channel can manage only one buffer. In addition, due to interrupt-processing delays built into the PIC, the CPU, and possibly your kernel, there isn't time to program a new buffer into the DMA controller when you receive an interrupt indicating that the previous one has been exhausted--if you try this, you will hear choppy sound, receive a poor grade, etc.

So, what can you do? The solution is to divide your one actual buffer into multiple "sub-buffers". You tell the DMA controller about the entire buffer, but you tell the sound card to interrupt you after some fraction of the buffer has been consumed. If you keep track of which sub-buffer generated the current interrupt, you know which sub-buffer the sound card is now working on; you can refill the sub-buffer which it has just exhausted. See the P4 lecture for a picture.

Into how many sub-buffers should you divide your one buffer? That is a design decision, and you may wish to explore the effect of different sub-buffer fractions. Ethernet devices typically have at least eight sub-buffers. Graphics devices frequently use exactly two buffers, so that the host and the drawing engine aren't slowed down by contending for the same DRAM "pages" (not the same as virtual memory pages) at the same time. The two-buffer case is often referred to as ping-pong buffering.

If this is difficult to understand, don't worry; it's not an easy concept. Dividing the buffer surely isn't as simple as having distinct buffers, like you might do with a video card (or a sane DMA setup...). It will probably help if you draw how the buffer starts off, and what happens after a few interrupts have happened.

Testing your driver

For your debugging ease, there are three methods with which you can test your driver.

  1. As usual, we provide a Simics set up. Because the sb16 module which ships with Simics is terribly broken, one of your TA's (Joshua) has written a replacement SoundBlaster emulation driver known as sb16_410. To run your code in a sound-enabled environment, run instead of

    We have tested the sb16_410 module to work on the Wean 5205 and 5207 cluster machines. If you have problems getting sound output from those machines, use the command alsamixer to turn up the volume -- make sure that the Master and PCM adjusts are set above their minimum values.

    The module will try to catch many common errors or unsupported behaviors that you might attempt as a driver writer and display error messages. If you believe that the module is in error, and that what you're doing should in fact be supported, please send e-mail to course staff.

  2. We have fitted the 15-410 Crash Machine with a genuine antique Creative Labs Vibra 16 ISA/PNP sound card, which purports to be Sound Blaster 16 compatible. A driver implemented with the specification given here should work fine on the crashbox. If you have difficulties running your driver on the crashbox, but your driver runs fine in Simics, contact the course staff and we'll see if we can figure out what the discrepancy is. Please be considerate of the other users of the cluster near the crashbox; don't leave snd_basic running at full volume for extended periods of time, etc.

  3. And, much to Professor Eckhardt's discontent, we mention QEMU as a possible means of testing your driver. Please read the QEMU warnings found on the course web site; QEMU is nowhere near as accurate as Simics, and recent versions have been known to behave incorrectly on operating systems that run properly on real hardware. To mitigate these effects, we recommend using a QEMU no later than version 0.9.0 to run your kernel. To enable Sound Blaster 16 support in QEMU (and to load your kernel), run qemu with a command like: ~jwise/qemu/bin/qemu -fda bootfd.img -soundhw sb16. QEMU is not an officially supported means of testing your kernel; we will not be responsible if it finds errors in your kernel that you've never seen before, finds errors in your kernel that don't actually exist, causes your computer to catch on fire, kills your cat, causes the NetHack gods to become angry with you, etc. Use at your own risk and discretion.

Getting Started

  1. Begin with a copy of your p3 directory tree. On top of it, extract the contents of the P4 tar file (the result should be that you gain a README and that is replaced).
  2. Do an update and marvel at the new files which arrive.
  3. Read through this document in its entirety. At least briefly peruse the P4 lecture if you did not attend it.
  4. Begin designing and coding! A suggested division of labor is for one partner to write some code to interface with the physical hardware (and feed it constant frames that produce at least a means of verification; you may wish to look at the sample waveform included in snd_basic), and for the other partner to write the system calls, temporarily hacking the timer interrupt handler so that it consumes a sound buffer on each timer interrupt. Once both partners have a reasonable semblance of working code, then you may wish to begin merging to get output from an application to the sound card.

Design considerations

Here are some issues you may wish to consider during your design process... how you address these issues will probably affect your project grade, but not by more than 10%.

  1. How can you make sure that just the right number of samples come out when you're close to running out?
  2. How can you deal with playing quantities of samples that are not a multiple of your DMA buffer size?
  3. What features of your kernel might cause sound-generating applications, which tend to require a lot of CPU cycles, to fail to run quickly enough?


  1. Your modified source tree, in p4, including your modified kernel and any tests you may have written. Don't forget to make clean.
  2. Include a discussion of your design and implementation in README.dox. Be sure to discuss the key design decisions you made.

Some test code

The following test programs have been provided for your enjoyment.

  • snd_basic: A basic acceptance test that produces a 440Hz square wave until the end of time. You can use this to verify that you get any output at all.
  • karplus_strong: Professor Dannenberg has written a simple synthesizer that produces a tone similar to what you might get by plucking a string. You can use this to verify that output appears to be coming out roughly in order.
  • karplus_seq: Professor Dannenberg has also composed a piece of music (the 15-410 theme song?) using this string synthesis technique.
  • snd_mp3: We've also given you an MP3 decoder using the libmad library for integerized MP3 decoding (which means that your kernel does not need to support floating point). Empirically, we've found that this test is too slow to run in realtime on Simics (but it might give your driver a good workout for pausing and unpausing the card!). However, it appears to work fine in QEMU and on real hardware. We've compiled in an MP3 of a short demo tune, "Vulpine Skyflight", composed by Seth Peelle (aka BeaT), who has graciously allowed us to use it for this project. Also in the directory is a C program to generate new include files from your own MP3s.

If you write any tests that do interesting things that you'd like to share with the class, please e-mail course staff and let us know; we'd be happy to redistribute them if they don't give away too much about the implementation. (Please don't just post them to .qa; let us have a chance to look over them first.)

Have Fun!

Make sure to have some fun with this project. You've earned it, right?

Specification Errata

  • v0: Initial public release.
  • v1: Fixed error in command queues. Clarified command queue section.
  • v2: Fixed another typo in command queues.
  • v3: Fixed an error in Playing Audio, adding the words minus one.

[Last modified Friday November 21, 2008]