Kernel
Audio
Audio playback supports AC97 and High Definition Audio controllers (src/drivers/sound/). sound_init() initializes both, prefers HDA, and allocates a 2 MiB streaming ring; all operations dispatch to the active card. Playback is polled — neither driver uses interrupts — and sound_poll() is pumped from the kernel idle loop, which is what keeps both whole-file and streaming playback fed.
Common Model
- Stream configuration: sample rate, channels, bits.
- File playback by extension (
SYS_SOUND_PLAY):.wav(PCM-only parser, RIFF chunk scan),.mp3(parser stub — not implemented), otherwise raw PCM at 22050 Hz stereo. - Buffer descriptor lists: 32 entries of 32 KB each in both drivers. Legacy whole-file playback copies up to one ring of the source up front; the poll function refills consumed entries from the source buffer as the hardware plays them.
- Volume:
sound_set_volume(0-100)programs the card's master volume (NAM master volume on AC97, output widget gain on HDA).
Streaming Playback
Userspace decodes audio and pushes PCM instead of buffering whole files (src/drivers/sound/sound.cpp):
| Call | Behavior |
|---|---|
SYS_SOUND_STREAM_OPEN(rate, channels, 16) |
Stops any current playback, configures the card, opens the stream. 16-bit only; 1 or 2 channels. |
SYS_SOUND_WRITE(data, size) |
With a stream open, blocks until every byte is queued in the ring. Classic-call ABI: any streaming failure (stream stopped under the writer, fatal signal) returns -1; the kernel's internal -EPIPE/-EINTR distinction is not observable in userspace. Without a stream open it fails (-1) instead of falling back to whole-buffer playback — raw-buffer playback belongs to SYS_SOUND_PLAY. |
SYS_SOUND_STREAM_END |
No more data; drain what is queued, then auto-close. A stream that never queued data and never started DMA is closed immediately: it is drained by definition. |
SYS_SOUND_STOP / SYS_SOUND_PAUSE / SYS_SOUND_RESUME |
Transport control; pause/resume are idempotent. A pause arriving while the card is not yet clocking the ring (pre-fill window, transient underrun stop) is latched in the stream and honored by the next DMA start, so it is never silently dropped. |
SYS_SOUND_STATUS |
Fills sound_status (include/uapi/sound.h): played/queued bytes in source format, state flags, and owned — whether the stream belongs to the calling process's group (the stream is single and global; another opener resets yours). |
SYS_SOUND_VOLUME(level) |
Card master volume, 0-100. |
Mechanics:
- The dispatcher ring is 2 MiB. DMA starts once a full card ring (1 MiB = 32 x 32 KiB) is queued, so playback never begins with silence;
STREAM_START_THRESHOLDgates this. A stream end with less queued flushes immediately. A latched pause defers the start until resume. - The drivers run in
stream_mode: BDL refills pull from the ring through a refill callback instead of a fixed source buffer. An exhausted ring pads silence and the card stops when the last real byte is consumed; the dispatcher restarts the card when enough new data is queued (underrun recovery) or closes the stream after a drainedSTREAM_END. Restarts also honor a pause latched while the card was stopped. - The stream records the group that opened it (leader pid). When that group fully exits, the kernel stops the stream and wakes blocked writers — a dead owner cannot leave the card clocking audio (serial marker:
sound: stopping the stream an exited process left open). - An ended stream auto-closes only when its ring is fully drained: a pause during the final drain also stops the card transiently, and that must not close the stream while data is still queued.
- AC97 DMAs stereo pairs, so mono streams are upmixed to stereo in the dispatcher; HDA passes the source channel count through.
- Writers block on a wait queue while the ring is full and are woken by the refill path, so playback never depends on holding a whole decoded file in memory. Blocking writers rely on the idle-loop pump advancing the card.
AC97
src/drivers/sound/ac97/ac97.cpp (PCI class 0x04 / subclass 0x01):
- NAM (BAR0) and NABM (BAR1) I/O registers; cold reset through the NABM global control.
- Variable sample rate detection via the extended capabilities register.
- PCM-out stream: BDL base, last-valid index, start/stop/reset controls. Volume via the NAM master volume register (inverted attenuation).
- Playback only.
HDA
src/drivers/sound/hda/hda.cpp (PCI class 0x04 / subclass 0x03), MMIO BAR0:
- Controller reset, STATESTS codec scan, CORB/RIRB command rings (256 entries each) for codec verbs.
- Known limitation: the CORB/RIRB exchange times out against QEMU's
intel-hda(no codec response ever lands in the RIRB, sohda_initgives up atfailed to find codec); the QEMU run targets therefore attach an AC97 card. Verified working in QEMU: AC97; HDA is untested against real hardware. - Codec/widget tree discovery: AFG nodes, pin/mixer/output widgets, EAPD, amplifier gain setup.
- Playback and recording streams (output stream chosen after input streams); per-node channel volume.
Userspace Path
| Command / call | Route |
|---|---|
sound <file> (shell) |
SYS_SOUND_PLAY — kernel-side parser picks WAV/MP3/PCM |
play <file> (shell) |
Userspace WAV parser, then SYS_SOUND_STREAM_OPEN + SYS_SOUND_WRITE + SYS_SOUND_STREAM_END through the streaming ring (payload floored to whole sample frames) |
| musicplayer / app code | SYS_SOUND_STREAM_OPEN + SYS_SOUND_WRITE + SYS_SOUND_STREAM_END, transport via SYS_SOUND_STATUS/PAUSE/RESUME/STOP |