Nexoniarz is the sole author and SchOS was never published under Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text, every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE, README.md, docs/licensing.md and the site say so. projects/schfs-linux follows the rest of the tree. The GPL gives copyleft: a fork has to ship its source. It also lifts the old "Linux drivers, never" rule, since GPL-2.0-only code combines with GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still has to pick one version, so docs/licensing.md now keeps Apache-2.0 and GPLv3 code out of the kernel to keep Linux code possible there. BSD 4-clause code goes from "avoid" to "no", because the advertising clause is GPL-incompatible. LDLS still ships no .ko. That boundary is now project policy rather than a legal requirement, and ldls.md, devices.md and licensing.md section 6 say so. refmod declares license=GPL, and ldls-check passes (27 checks). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.7 KiB
Audio
A program that wants to make a sound opens /dev/dsp, writes PCM to it, and
closes it. That is what UNIX audio has always been, and it is what this is.
tone 440 2 # 440 Hz for two seconds
Three pieces, in the order a sample passes through them:
/dev/dsp |
kernel/dev/audio.c. A character device: write() is audio, and a handful of OSS ioctls say what the samples are. |
| AudioEngine | The same file. Everything above a driver that is not specific to a chip: the format, whether there is room, and what a write does when there is not. |
| a driver | kernel/drivers/audio/ac97.c today. Bound by class, exactly as a display driver is — device_find_class(DEVCLASS_AUDIO). |
Why OSS and not something new
The ioctls are SNDCTL_DSP_SPEED, SNDCTL_DSP_CHANNELS, SNDCTL_DSP_SETFMT,
SNDCTL_DSP_SYNC and SNDCTL_DSP_GETOSPACE, with OSS's own numbering. They
are not invented here and they are not copied for nostalgia: every UNIX audio
program that has ever been written knows them, and the only thing a different
set would achieve is making ports harder.
They are read/write ioctls, which is the part that matters. A program asks
for 44100; the device sets 48000 and writes 48000 back; the program resamples.
One that ignores the answer plays everything 9% fast, and that is its bug
rather than the driver's. configure in the class works the same way, and for
the same reason — a driver that can only do 48 kHz says so there, rather than
by quietly failing a write() later.
What the engine is, and what it deliberately is not
It owns format negotiation, the "is there room" question, and blocking. Those are genuinely shared: a second driver should not have to re-implement wait until the speaker has caught up.
It is one stream. Mixing several programs into one device is a real feature with a real design behind it, and building it before anything had played a single sound would be exactly the mistake ../plan/1.0.md keeps a register for: work that exists and is connected to nothing. The second program that wants to play at the same time is what should cause it to be written.
Waiting on the tick, not on the device
audio_wait() sleeps until the next timer tick rather than on an interrupt
from the sound card, which looks like the lazy choice and is not.
A ring that drains is a ring with room in it, so asking again in ten milliseconds always makes progress. Sleeping on the device would make audio depend on the interrupt arriving — and on a machine whose line is misrouted, or a chip that needs a kick, that is not a stutter, it is a writer that never wakes up. The interrupt handler is still there and still accounts for finished buffers; the point is that deleting it would cost efficiency and not correctness.
AC'97 first, and HDA later
AC'97 is the simplest audio device that exists both on real machines and in QEMU. Intel HDA is what a laptop built this century actually has, and it is a much larger driver — codec enumeration, CORB/RIRB, stream descriptors. Writing it first would have meant writing two unproven things at once: a driver, and the interface it is the only user of.
The class is the whole point of the order. HDA arrives as a second
struct audio_ops and nothing above it changes.
How it plays
The chip reads a buffer descriptor list: 32 entries, each naming a physical
address and a length. It plays the entry at CIV and stops when it reaches
LVI, the last valid index. Playing continuously is therefore not "start it
and walk away" — it is keeping LVI ahead of CIV, which is what queue()
does, one 4 KB descriptor at a time.
Retiring finished descriptors happens in both queue() and the interrupt
handler, through the same tail pointer so the two cannot disagree. That is
the same "the interrupt is an optimisation" stance as above.
Both register files are I/O ports, not MMIO — which is why nothing in the
driver maps anything. bar[n].is_io is what says so, and a driver that
ioremaped these would be mapping a port number as though it were an address.
How it is tested
QEMU's wav audio backend writes what the guest plays to a file, so the test
is not "it did not crash":
make qemu # QEMU_AUDIO puts an AC97 on every target
tone 440 2
and the file is measured — 1.97 seconds of 44.1 kHz stereo, peak amplitude 14177 against the tone generator's table maximum of 14178, and 880 zero crossings per second, which is 440 Hz exactly.
wav rather than a host backend on purpose: a build machine may have no sound
card, no PulseAudio and no session bus, and it is the only backend whose output
can be checked. To actually hear it:
make qemu QEMU_AUDIO='-audiodev pa,id=snd0 -device AC97,audiodev=snd0'
What it does not do
- Recording.
read()on/dev/dspreturnsENOSYSrather than zero, because zero is end-of-file and a recorder would write an empty file and call it a success. - Mixing. One stream; see above.
- Mono. The driver reports two channels because AC'97 plays stereo, and a mono stream would have to be doubled somewhere. The write-back tells the caller, which is the whole reason the ioctl has a direction.
- Formats other than signed 16-bit little-endian. It is what every PC audio chip takes natively and what a WAV file already contains. Converting in the kernel to satisfy a caller that could have converted first is not an obvious trade.
- Volume. The mixer is set to full and left there. How loud a machine is is not the kernel's decision, and a device that comes up silent reads as broken to whoever is testing it.