Currently Plan 9 supports SoundBlaster 16 audio device driver that has become obsolete and not available on most modern day machines. Most modern machines have an integrated soundcard on their motherboard. The most common is the AC'97 (Audio Codec '97); Intel Corporation's Audio Codec standard used mainly in Motherboards as integrated sound cards. AC'97 Specification Revision 2.2This standard consists of three components:
Audio Codec - is the actual unit responsible for converting bits into audio signals and vice-versa. It comprises of the Digital to Analog (DAC) and Digital to Analog (ADC) converters. It gives the analog audio output on LINE_OUT, AUX_OUT and MONO_OUT (optional). It takes in analog stereo input from LINE_IN and mono input from MIC_IN.
AC'97 Digital Controller - is an independent module and varies in features and implementation from vendor to vendor. The basic function of the Controller is to "control" the Codec by communication with the Codec over the AC link. Since the AC'97 Digital Controllers vary in features and implementation, so do their respective drivers. After all, the Controller is the one that is responsible for reading from and writing to the Codec. We have implemented the driver for Intel 82801AA and 82801AB I/O Controller Hub (ICH/ICH0). The ICH family was chosen because it is the most prolific AC'97 controller. It started off from ICH0 and has now reached ICH7. It is very well documented and easily available.
AC-Link - is a bidirectional, fixed rate, serial PCM (Pulse Code Modulation) digital stream employing a TDM (Time Division Multiplexing) scheme. It is the communication link between the Codec and the Controller. The AC'97 specification describes the exact protocol to communicate with a Codec via the AC Link
We have implemented a device driver for the ICH Controller that controls the AC'97 Codec via the AC-Link interface. The API for writing device drivers in Plan 9 is well-structured so that we had to write functionality for specific methods like ac97init, ac97attach, ac97read, ac97write, ac97open, ac97close, etc.
We have implemented following 3 channels:
When the ac97write is called for playing audio, we copy the buffer passed from user space into the memory region pointed by the first empty slot of the Buffer Descriptor List (BDL). The BDL list proceeds in a circular fashion with each entry pointing to memory region which we allocate during initialization. We then start the DMA engine which then reads that data from memory and starts transfering it to the Codec through the AC-link. The Codec has DAC (Digital To Analog) engines that converts the digital pulses into analog pulses and sends them out on the Master Line Out. As soon as processing of one buffer completes, an interrupt arrives which causes interuppt service routine to be invoked. We do the bookkeeping to mark the buffer as empty, and the DMA engine then continues with the next available buffer in line till it reach the Last Valid Buffer.
The way we have designed the interface for AC'97 Codec and vendor-specific controller in Plan9, there is a clear distinction in the code between the Controller and the Codec code. They interact in a very well-defined manner. This makes it is easy to add code for other vendor controllers as well and re-use Codec specific code, since it is independent of the controller being used.
We have allocated 32 physical buffers that so we could use all the buffers in the BDL. We could allocate them in the beginning and initialize the BDL. Also 32 buffers is enough to avoid any kind of lag. Since data has to be
memmoved into those buffers, it is an expensive operation. Since there are 32 buffers, audio is always catching up.
The maximum size that a buffer in the BDL can have is 128K. The reason we have such a small buffer size is that the default buffer size for write requests is 4KB. We don't wait for partially filled buffers to fill till completion before playing them out since that would add latency. So, imagine if we had a buffer size of 128KB but we kept getting write req. of size 4KB at a time. In this case, the remaining 124KB of each buffer would thus be wasted. Also 4KB is small enough so that we can allocate 32 of those.
We ran into a few hardware problems. We had trouble finding out that internal clock of the Controller could be buggy and would play out audio at an abnormal frequency. Once we found this out, we came to know this was a known problem in the ICH family. To fix this we had to follow a procdure, to measure the default rate and if it isn't what is expected, then calibrate the value and add a correction so that the audio play out at correct frequency.
Initially we were facing strange problems in adjusting the master volume, whereby when a particular value is written to the audio register, it would read back totally different value. We followed all the protocols of reading and writing to volume registers correctly, but it still gave problems. We spent lot of time figuring what we were doing wrong, but all in vain.
Eventually when we fixed the frequency to 44.1K typical for audio from the default 48K rate, and added the correction if required, the volume registers started working perfectly fine. We still don't know how the two are related. But it just worked.
Intel ICH/ICH0 uses IO-mapped IO to access codec registers. But Intel ICH4 onwards supports memory-mapped IO for faster access to codec registers. The controller driver uses backward-compatibility mode in ICH4 controllers onwards to continue using IO-mapped IO.
Implementation of the ICH Controller and Audio Codec driver is complete for audio playback.
We learned that hardware can give weird problems at times and being software people, it is hard to cope with it. But it was our first time and we had a great learning experience. We hope someone will follow-up and implement support for recording as well as other vendor controllers.