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
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
To get sound data from user space into the kernel, you will implement the following system calls:
The buffer that your kernel maintains with
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:
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:
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.
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:
DMA Controller Addresses
All of these addresses represent single-byte I/O ports, to be accessed with the inb/outb calls.
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.
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%.
Some test code
The following test programs have been provided for your enjoyment.
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.)
Make sure to have some fun with this project. You've earned it, right?
[Last modified Friday November 21, 2008]