audio: /dev/dsp, AudioEngine, and an AC'97 under it

A program that wants to make a sound opens /dev/dsp, writes PCM, and closes
it. That is what UNIX audio has always been and it now exists here:

    tone 440 2

Three pieces, in the order a sample passes through them. **/dev/dsp** is a
character device where write() is audio and a handful of OSS ioctls say what
the samples are. **AudioEngine** is 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** is bound by class, exactly as a display driver
is: device_find_class(DEVCLASS_AUDIO).

OSS numbering rather than something new, because every UNIX audio program ever
written already knows it 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 9% fast, and
that is its bug rather than the driver's.

**The engine is deliberately one stream.** Mixing several programs is a real
feature with a real design behind it, and building it before anything had
played a single sound would be exactly what docs/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.

**audio_wait() sleeps on the timer tick, not on the device.** That looks like
the lazy choice and is not: a ring that drains is a ring with room, so asking
again in ten milliseconds always makes progress, whereas sleeping on the card
makes audio depend on an interrupt arriving. On a machine whose line is
misrouted that is not a stutter, it is a writer that never wakes. The handler
still accounts for finished buffers -- deleting it would cost efficiency, not
correctness -- and it retires descriptors through the same `tail` pointer
queue() uses, so the two cannot disagree about what is in flight.

**AC'97 first because HDA is two unproven things at once.** It is the simplest
audio device that exists on real machines and in QEMU both. Intel HDA is what
the laptop actually has and is a much larger driver; writing it against an
interface nothing had ever played through would have meant debugging the
driver and the interface simultaneously. HDA arrives as a second
struct audio_ops and nothing above it changes -- which is the entire point of
the class.

Two things the wiring needed: PCI class 0x04 now maps to DEVCLASS_AUDIO (the
driver bound and /proc/devices showed it, but audio_get() found nothing,
because the class byte had never been translated), and include/x86.h grew
inw() -- AC'97's mixer is all 16-bit registers and reading one a byte at a
time returns the low half twice on some codecs.

**Tested by measuring what came out, not by not crashing.** QEMU's wav backend
writes the guest's audio to a file; `tone 440 2` produced 1.97 seconds of
44.1 kHz stereo with peak amplitude 14177 against the generator's table
maximum of 14178, and 880 zero crossings per second -- 440 Hz exactly. wav is
on every QEMU target rather than a host backend, because a build machine may
have no sound card and it is the only backend whose output can be checked.

Both supported configurations: one boot each, no traps, no panics, socktest
and memfdtest green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-09-24 14:23:13 +02:00
commit 9fea18b18b
18 changed files with 1110 additions and 5 deletions

View file

@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \
KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \ KERN_DEV = $(addprefix $(BUILD)/kernel/dev/, \
bio.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o keymap.o mouse.o \ bio.o cdev.o device.o disk.o fb.o ide.o input.o irq.o kbd.o keymap.o mouse.o \
pci.o pciuser.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o \ pci.o pciuser.o text.o ttf.o uart.o gfx.o vt.o ramdisk.o procfs.o \
dbg.o netuser.o) dbg.o netuser.o audio.o)
# Drivers for hardware that has to be *found* rather than assumed. Anything # Drivers for hardware that has to be *found* rather than assumed. Anything
# discovered by walking a bus belongs here; the fixed-address legacy devices # discovered by walking a bus belongs here; the fixed-address legacy devices
@ -43,6 +43,7 @@ KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_pci.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_fb.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/vga/vga_fb.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o KERN_DRIVERS += $(BUILD)/kernel/drivers/gpu/virtio_gpu/virtio_gpu.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o KERN_DRIVERS += $(BUILD)/kernel/drivers/net/e1000/e1000.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/audio/ac97.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o KERN_DRIVERS += $(BUILD)/kernel/drivers/bus/xhci/xhci.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/core/usb.o
KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o KERN_DRIVERS += $(BUILD)/kernel/drivers/usb/hid/usb_hid.o
@ -108,7 +109,7 @@ KERN_NOENTRY = $(filter-out $(BUILD)/kernel/arch/x86_64/entry.o, $(KERNOBJS))
# Third-party software does NOT go here — see ports/. # Third-party software does NOT go here — see ports/.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o) ULIB = $(addprefix $(BUILD)/lib/, start.o glue.o usys.o)
CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd CMDS = schinit schinitd fbinfo shmtest socktest inputtest nettest reflink schattr ping chvt loadkeys sigtest memfdtest pcitest vgpud schdbg schdbgd resolvconf httpd schmode netecho ldlsd tone
CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS)) CMDBINS = $(addprefix $(BUILD)/cmd/, $(CMDS))
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -194,6 +195,18 @@ QEMU_DISPLAY = -vga std -device virtio-gpu-pci
# claim and exits, and the single window is the thing you wanted to look at. # claim and exits, and the single window is the thing you wanted to look at.
QEMU_DISPLAY_1 = -vga std QEMU_DISPLAY_1 = -vga std
# Sound, on every QEMU target.
#
# `wav` rather than a host audio backend: a build machine may have no
# soundcard, no PulseAudio and no session bus, and a QEMU that fails to open
# one of those prints a warning per voice and carries on -- which is noise in
# every boot log for a feature most runs are not testing. Writing to a file
# works everywhere and is the only backend that can be *checked*: the audio
# test plays a tone and measures the zero crossings of what came out.
#
# To actually hear it: make qemu QEMU_AUDIO='-audiodev pa,id=snd0 -device AC97,audiodev=snd0'
QEMU_AUDIO ?= -audiodev wav,id=snd0,path=$(BUILD)/qemu-audio.wav -device AC97,audiodev=snd0
# A network card, on every QEMU target. # A network card, on every QEMU target.
# #
# Spelled out rather than left to QEMU's default NIC. The default exists and is # Spelled out rather than left to QEMU's default NIC. The default exists and is
@ -643,7 +656,7 @@ ovmf: $(OVMF_CODE) $(OVMF_VARS)
all: $(OVMF_CODE) $(OVMF_VARS) all: $(OVMF_CODE) $(OVMF_VARS)
qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) qemu-uefi: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
$(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk,format=raw -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 $(QEMU_ACCEL) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) $(QEMU_NET) $(QEMU) -machine pc-i440fx-9.2 -drive if=pflash,format=raw,unit=0,readonly=on,file=$(OVMF_CODE) -drive if=pflash,format=raw,unit=1,file=$(OVMF_VARS) -drive file=fat:rw:$(BUILD)/esp,index=0,media=disk,format=raw -drive file=$(BUILD)/schfs.img,format=raw,index=1,media=disk -m 1G -smp 1 $(QEMU_ACCEL) -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) $(QEMU_NET) $(QEMU_AUDIO)
# Build the ramdisk-root variant. This is a target rather than just # Build the ramdisk-root variant. This is a target rather than just
# "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT), # "make RAMROOT=1" because toggling RAMROOT changes CFLAGS (-DRAMDISK_ROOT),
@ -1356,7 +1369,7 @@ qemu-gdb: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
-S -gdb tcp::$(GDBPORT) -S -gdb tcp::$(GDBPORT)
qemu-kvm: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) qemu-kvm: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)
$(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) $(QEMU_NET) -cpu host \ $(QEMU) -enable-kvm -serial mon:stdio $(QEMU_DISPLAY) $(QEMU_INPUT) $(QEMU_NET) $(QEMU_AUDIO) -cpu host \
$(OVMF_DRIVES) -m 1G -smp $(SMP) -machine pc-i440fx-9.2 $(OVMF_DRIVES) -m 1G -smp $(SMP) -machine pc-i440fx-9.2
qemu: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) qemu: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS)

134
cmd/tone/main.c Normal file
View file

@ -0,0 +1,134 @@
// tone -- write a sine wave to /dev/dsp.
//
// The smallest program that proves the audio path end to end: it asks the
// device what it can play, generates that, and writes it. If a machine makes
// this noise, every layer between a write() and a speaker is working.
//
// tone 440 Hz for one second
// tone 1000 1000 Hz for one second
// tone 1000 3 1000 Hz for three seconds
//
// Deliberately not a WAV player. A player has to parse a header, and a header
// is a second thing to get wrong while testing the first.
#include "types.h"
#include "user.h"
#include "fcntl.h"
#include "audio.h"
// A quarter-amplitude sine, from a table rather than a libm that does not
// exist here. 64 points is audibly a sine and arithmetically exact.
static const short SINE[64] = {
0, 803, 1605, 2404, 3196, 3980, 4753, 5511,
6254, 6977, 7679, 8357, 9008, 9630, 10220, 10777,
11297, 11780, 12222, 12623, 12980, 13293, 13559, 13778,
13951, 14075, 14151, 14178, 14156, 14085, 13965, 13797,
13581, 13318, 13009, 12655, 12257, 11818, 11338, 10820,
10266, 9679, 9060, 8413, 7740, 7044, 6329, 5597,
4851, 4096, 3334, 2569, 1804, 1043, 289, -453,
-1180, -1889, -2576, -3239, -3874, -4479, -5051, -5588
};
// lib/ has no stdio: write(2) is the whole of it, which is why every
// first-party program in cmd/ carries these three lines.
static void
say(const char *s)
{
int n = 0;
while (s[n]) n++;
write(1, (void *)s, n);
}
static void
sayn(int v)
{
char b[12];
int i = 0, j;
if (v == 0) { say("0"); return; }
if (v < 0) { say("-"); v = -v; }
while (v && i < 11) { b[i++] = (char)('0' + v % 10); v /= 10; }
for (j = i; j-- > 0; )
write(1, &b[j], 1);
}
static int
num(const char *s)
{
int v = 0;
while (*s >= '0' && *s <= '9')
v = v * 10 + (*s++ - '0');
return v;
}
int
main(int argc, char *argv[])
{
int hz = argc > 1 ? num(argv[1]) : 440;
int secs = argc > 2 ? num(argv[2]) : 1;
int rate = 48000, channels = 2, fmt = AFMT_S16_LE;
short buf[1024]; /* 512 stereo frames */
int fd, phase = 0;
if (hz < 20 || hz > 20000) {
say("tone: "); sayn(hz); say(" Hz is not audible\n");
return 1;
}
if (secs < 1 || secs > 30)
secs = 1;
fd = open("/dev/dsp", O_WRONLY);
if (fd < 0) {
say("tone: no /dev/dsp -- is there an audio device?\n");
return 1;
}
// Ask, then believe the answer. These are read/write on purpose: the
// device writes back what it actually set, and a program that ignores
// that plays at the wrong speed and blames the driver.
if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || fmt != AFMT_S16_LE) {
say("tone: the device will not take 16-bit samples\n");
return 1;
}
ioctl(fd, SNDCTL_DSP_CHANNELS, &channels);
ioctl(fd, SNDCTL_DSP_SPEED, &rate);
say("tone: "); sayn(hz); say(" Hz for "); sayn(secs);
say(" s at "); sayn(rate); say(" Hz, "); sayn(channels);
say(" channel(s)\n");
// The phase step in 1/256ths of a table entry, so a 440 Hz tone at 48 kHz
// is not quantised into something a tuner would argue with.
int step = (hz * 64 * 256) / rate;
int frames_total = rate * secs;
while (frames_total > 0) {
int frames = frames_total > 512 ? 512 : frames_total;
int i;
for (i = 0; i < frames; i++) {
short s = SINE[(phase >> 8) & 63];
phase += step;
if (channels == 2) {
buf[i * 2] = s;
buf[i * 2 + 1] = s;
} else {
buf[i] = s;
}
}
int bytes = frames * channels * 2;
if (write(fd, buf, bytes) != bytes) {
say("tone: short write -- the device stopped taking audio\n");
close(fd);
return 1;
}
frames_total -= frames;
}
// Wait for it to finish rather than exiting on top of it. close() drains
// too, but saying so is the point of the ioctl existing.
ioctl(fd, SNDCTL_DSP_SYNC, 0);
close(fd);
return 0;
}

View file

@ -92,6 +92,7 @@ Read in this order if you are new:
| [graphics.md](graphics.md) | Framebuffer, text rendering, the console, and PSOGM. | | [graphics.md](graphics.md) | Framebuffer, text rendering, the console, and PSOGM. |
| [terminals.md](terminals.md) | The six virtual terminals, and the chord that switches them. | | [terminals.md](terminals.md) | The six virtual terminals, and the chord that switches them. |
| [networking.md](networking.md) | NIC drivers, lwIP, and both socket families. | | [networking.md](networking.md) | NIC drivers, lwIP, and both socket families. |
| [audio.md](audio.md) | `/dev/dsp`, AudioEngine, and the AC'97 driver under it. |
| [init.md](init.md) | `schinit`, PID 1 as a role, and the job manifests. | | [init.md](init.md) | `schinit`, PID 1 as a role, and the job manifests. |
| [debugging.md](debugging.md) | Symbols, a log that outlives the kernel, and asking the kernel a question. | | [debugging.md](debugging.md) | Symbols, a log that outlives the kernel, and asking the kernel a question. |
| [ldls.md](ldls.md) | Running somebody else's drivers: a Linux `.ko`, in a process, reaching hardware through `/dev/pci`. | | [ldls.md](ldls.md) | Running somebody else's drivers: a Linux `.ko`, in a process, reaching hardware through `/dev/pci`. |

124
docs/architecture/audio.md Normal file
View file

@ -0,0 +1,124 @@
<!-- SPDX-License-Identifier: Apache-2.0 -->
# 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.
```sh
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](../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
`ioremap`ed 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":
```sh
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:
```sh
make qemu QEMU_AUDIO='-audiodev pa,id=snd0 -device AC97,audiodev=snd0'
```
## What it does not do
- **Recording.** `read()` on `/dev/dsp` returns `ENOSYS` rather 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.

103
include/audio.h Normal file
View file

@ -0,0 +1,103 @@
#pragma once
// The audio class, and the engine above it.
//
// A program that wants to make a sound should be able to open a device, write
// samples to it, and stop -- which is what UNIX audio has always been, and
// what this is. `/dev/dsp`, PCM in, `write()`, and a handful of ioctls to say
// what the samples are. No server, no session bus, no protocol.
//
// The shape is the display class's, for the same reason: a consumer asks for
// "the audio device" and gets whatever is bound, so adding a second driver is
// not an edit to every caller. Found with device_find_class(DEVCLASS_AUDIO).
//
// ---------------------------------------------------------------------------
//
// **What the engine is for, and what it is not yet.**
//
// AudioEngine owns everything above a driver that is not specific to a chip:
// what format the stream is in, how many bytes the hardware can take, and what
// a write() does when it cannot take them all. That is genuinely shared -- a
// second driver should not re-implement "block until there is room".
//
// It is deliberately *one stream*. Mixing several programs into one device is
// a real feature with a real design behind it, and building it before anything
// plays a single sound would be the mistake docs/plan/1.0.md warns about: 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.
struct device;
// Interleaved signed 16-bit little-endian, which is what every PC audio chip
// takes natively and what a WAV file already contains. Others exist and none
// of them is needed to make a sound; converting in the kernel to satisfy a
// caller that could have converted first is not an obvious trade.
#define AUDIO_FMT_S16LE 16
struct audio_format {
unsigned int rate; // frames per second -- 48000, 44100
unsigned int channels; // 1 mono, 2 stereo
unsigned int bits; // AUDIO_FMT_S16LE
};
struct audio_ops {
// Take this format, or report what the hardware will do instead by writing
// back into `f` and returning 0. Returning negative means it cannot play at
// all. A driver that only does 48 kHz stereo says so here rather than
// failing a write() later, which is the difference between a program that
// adapts and a program that gets silence.
int (*configure)(struct device *, struct audio_format *f);
// Bytes that can be queued right now without waiting. The engine uses this
// to decide whether a write() has to block, so a driver that returns a
// hardware-accurate number is a driver whose write() latency is honest.
int (*space)(struct device *);
// Queue PCM. Returns bytes accepted, which may be fewer than offered and
// may be zero; the engine handles the remainder. Never blocks.
int (*queue)(struct device *, const void *pcm, int bytes);
// Start the stream if it is not running. Called by the engine once there is
// something to play, rather than at open: a device left running with an
// empty ring plays whatever was in it, which on real hardware is a loop of
// the last buffer and sounds exactly as bad as it reads.
void (*start)(struct device *);
// Stop, and discard anything queued.
void (*stop)(struct device *);
// Bytes still to be played. Zero means the hardware has finished. The
// engine polls this for SNDCTL_DSP_SYNC rather than a driver growing its
// own wait loop.
int (*pending)(struct device *);
};
// --- what the kernel uses --------------------------------------------------
struct device *audio_get(void);
int audio_configure(struct audio_format *f); // 0, or negative
int audio_write(const void *pcm, int bytes); // bytes queued, or negative
void audio_drain(void);
void audio_stop(void);
// --- what userspace uses ---------------------------------------------------
//
// OSS numbering, which is the one every UNIX audio program already knows and
// the only reason to invent a different set would be to make ports harder.
// These four are the whole interface a tone generator or a WAV player needs.
#define SNDCTL_DSP_SPEED 0xC0045002 // int in/out: sample rate
#define SNDCTL_DSP_CHANNELS 0xC0045006 // int in/out: 1 or 2
#define SNDCTL_DSP_SETFMT 0xC0045005 // int in/out: AFMT_S16_LE
#define SNDCTL_DSP_SYNC 0x00005001 // wait for the queue to drain
#define SNDCTL_DSP_GETOSPACE 0x8010500C // struct audio_buf_info out
#define SNDCTL_DSP_RESET 0x00005000 // stop, discard
#define AFMT_S16_LE 0x00000010
struct audio_buf_info {
int fragments; // how many whole fragments are free
int fragstotal; // how many there are in total
int fragsize; // bytes in one
int bytes; // free bytes, which is the number that matters
};

View file

@ -611,6 +611,13 @@ void fbclose(struct inode*);
// further syscalls, so it is the last the kernel hears about it. // further syscalls, so it is the last the kernel hears about it.
void fb_mapped(void); void fb_mapped(void);
// AudioEngine and /dev/dsp. kernel/dev/audio.c, include/audio.h.
int dspread(struct inode*, uint64, char*, int);
int dspwrite(struct inode*, uint64, char*, int);
void dspclose(struct inode*);
int dsp_ioctl(int request, uint64 argp);
void audio_wait(struct device *);
// The crash screen, on displays other than the console's. kernel/dev/device.c. // The crash screen, on displays other than the console's. kernel/dev/device.c.
void display_panic_present(void); void display_panic_present(void);
// Push the console's newly-drawn rows to a second display, if the kernel // Push the console's newly-drawn rows to a second display, if the kernel

View file

@ -46,6 +46,7 @@ enum dev_class {
DEVCLASS_STORAGE, DEVCLASS_STORAGE,
DEVCLASS_NET, DEVCLASS_NET,
DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs) DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs)
DEVCLASS_AUDIO,
DEVCLASS_NCLASS DEVCLASS_NCLASS
}; };

View file

@ -187,3 +187,5 @@ extern struct devsw devsw[];
#define CONSOLE 1 #define CONSOLE 1
// Major number of /dev/input/eventN. The minor is the device index. // Major number of /dev/input/eventN. The minor is the device index.
#define INPUT_MAJOR 7 #define INPUT_MAJOR 7
// Major number of /dev/dsp. See include/audio.h.
#define AUDIO_MAJOR 12

View file

@ -29,7 +29,7 @@
#define NOFILE 128 // open files per process #define NOFILE 128 // open files per process
#define NFILE 512 // open files per system #define NFILE 512 // open files per system
#define NINODE 200 // maximum number of active i-nodes #define NINODE 200 // maximum number of active i-nodes
#define NDEV 12 // maximum major device number #define NDEV 13 // maximum major device number
#define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c) #define RAMDISK_DEV 2 // memory-backed block device (kernel/dev/ramdisk.c)
#define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c) #define USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c)

View file

@ -40,6 +40,17 @@ outb(ushort port, uchar data)
asm volatile("out %0,%1" : : "a" (data), "d" (port)); asm volatile("out %0,%1" : : "a" (data), "d" (port));
} }
// 16 bits. Added for AC'97, whose mixer is entirely 16-bit registers: reading
// one of them a byte at a time returns the low half twice on some codecs,
// because the read of the low byte is what latches the pair.
static inline ushort
inw(ushort port)
{
ushort data;
asm volatile("inw %1,%0" : "=a" (data) : "d" (port));
return data;
}
static inline uint static inline uint
inl(ushort port) inl(ushort port)
{ {

284
kernel/dev/audio.c Normal file
View file

@ -0,0 +1,284 @@
// AudioEngine: the audio class, and /dev/dsp above it.
//
// See include/audio.h for what this is and what it deliberately is not. In
// short: a program opens /dev/dsp, says what its samples are, writes them, and
// stops. Everything here is either the class vtable's thin wrapper or the part
// of "play this" that is not specific to a chip.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "device.h"
#include "audio.h"
#include "proc.h"
#include "uaccess.h"
#include "errno.h"
static struct spinlock audio_lock;
static int audio_lock_init;
// The format the device is currently in. Kept here rather than asked of the
// driver on every write, because a caller that set 44100 and got 48000 needs
// to be told once and then have it stay told.
static struct audio_format current_fmt = { 48000, 2, AUDIO_FMT_S16LE };
static void
lock_once(void)
{
if (!audio_lock_init) {
initlock(&audio_lock, "audio");
audio_lock_init = 1;
}
}
// --- the class -------------------------------------------------------------
struct device *
audio_get(void)
{
return device_find_class(DEVCLASS_AUDIO);
}
static const struct audio_ops *
ops_of(struct device *d)
{
return d && d->ops ? (const struct audio_ops *)d->ops : 0;
}
int
audio_configure(struct audio_format *f)
{
struct device *d = audio_get();
const struct audio_ops *o = ops_of(d);
int r;
if (!o || !o->configure)
return -ENODEV;
if (f->bits != AUDIO_FMT_S16LE)
return -EINVAL;
if (f->channels != 1 && f->channels != 2)
return -EINVAL;
if (f->rate < 4000 || f->rate > 192000)
return -EINVAL;
r = o->configure(d, f);
if (r < 0)
return r;
lock_once();
acquire(&audio_lock);
current_fmt = *f; // what the driver settled on, not what was asked
release(&audio_lock);
return 0;
}
// Queue bytes, blocking while the hardware is full.
//
// The blocking is here rather than in a driver because it is the same in every
// driver: there is a ring, it has room or it does not, and a writer that is
// ahead of the speaker has nothing useful to do but wait. Sleeping on the
// device and being woken by its interrupt is the whole of it.
int
audio_write(const void *pcm, int bytes)
{
struct device *d = audio_get();
const struct audio_ops *o = ops_of(d);
const char *p = pcm;
int done = 0;
if (!o || !o->queue)
return -ENODEV;
if (bytes <= 0)
return 0;
while (done < bytes) {
int n = o->queue(d, p + done, bytes - done);
if (n < 0)
return done ? done : n;
if (n == 0) {
// Full. Start what is already queued -- a writer that fills the ring
// and then waits for space would otherwise wait for a stream nobody
// started -- and give the CPU up until some of it has been played.
if (o->start)
o->start(d);
if (myproc() && myproc()->killed)
return done ? done : -EINTR;
audio_wait(d);
continue;
}
done += n;
}
if (o->start)
o->start(d);
return done;
}
// Wait for the hardware to make room. A driver signals this from its
// interrupt handler with audio_wakeup().
//
// The timeout is not a nicety: a driver whose interrupt never arrives -- a
// misrouted line, a chip that needs a kick -- would otherwise hang the writer
// for ever with no way to tell what happened. Waking up anyway and asking the
// hardware again turns that into audio that stutters, which is a bug report
// somebody can make.
void
audio_wait(struct device *d)
{
uint t0;
(void)d;
// One timer tick, the same way kernel/sys/sys_poll.c waits: there is no
// timed sleep() in this kernel, and adding one for a single caller would be
// a scheduler change to avoid a ten-line loop.
//
// Sleeping on `ticks` rather than on the device means a driver interrupt is
// not strictly required to make progress -- which is the point. A ring that
// drains is a ring that has room, and asking again in ten milliseconds
// costs one wakeup per buffer.
acquire(&tickslock);
t0 = ticks;
while (ticks == t0 && !(myproc() && myproc()->killed))
sleep(&ticks, &tickslock);
release(&tickslock);
}
void
audio_drain(void)
{
struct device *d = audio_get();
const struct audio_ops *o = ops_of(d);
int spins;
if (!o || !o->pending)
return;
// Bounded, for the same reason the wait above is. Ten seconds of audio is
// longer than any buffer this engine will hold, so a `pending` that has not
// reached zero by then is a driver that is not playing rather than one that
// is busy.
for (spins = 0; spins < 1000 && o->pending(d) > 0; spins++) {
if (myproc() && myproc()->killed)
return;
audio_wait(d);
}
}
void
audio_stop(void)
{
struct device *d = audio_get();
const struct audio_ops *o = ops_of(d);
if (o && o->stop)
o->stop(d);
}
// --- /dev/dsp ---------------------------------------------------------------
int
dspwrite(struct inode *ip, uint64 off, char *src, int n)
{
(void)ip; (void)off;
return audio_write(src, n);
}
// Recording is not implemented, and says so. Returning zero would look like
// end-of-file and a recorder would write an empty file and call it a success.
int
dspread(struct inode *ip, uint64 off, char *dst, int n)
{
(void)ip; (void)off; (void)dst; (void)n;
return -ENOSYS;
}
// The last descriptor is closing. Anything still queued is played out first:
// a program that writes a sound and exits expects to hear the end of it, and
// dropping the tail is the one behaviour that would make this device feel
// broken while technically working.
void
dspclose(struct inode *ip)
{
(void)ip;
audio_drain();
audio_stop();
}
int
dsp_ioctl(int request, uint64 argp)
{
struct device *d = audio_get();
const struct audio_ops *o = ops_of(d);
struct audio_format f;
int val;
if (!o)
return -ENODEV;
lock_once();
acquire(&audio_lock);
f = current_fmt;
release(&audio_lock);
switch (request) {
case SNDCTL_DSP_SPEED:
case SNDCTL_DSP_CHANNELS:
case SNDCTL_DSP_SETFMT: {
if (copy_from_user(&val, argp, sizeof(val)) < 0)
return -EFAULT;
if (request == SNDCTL_DSP_SPEED) f.rate = (unsigned)val;
else if (request == SNDCTL_DSP_CHANNELS) f.channels = (unsigned)val;
else {
if (val != AFMT_S16_LE)
return -EINVAL;
f.bits = AUDIO_FMT_S16LE;
}
int r = audio_configure(&f);
if (r < 0)
return r;
// OSS writes back what was actually set, which is the entire reason these
// are read/write ioctls: a program asks for 44100, is told 48000, and
// resamples. One that ignores the answer gets a chipmunk, and that is its
// bug rather than ours.
val = (request == SNDCTL_DSP_SPEED) ? (int)f.rate
: (request == SNDCTL_DSP_CHANNELS) ? (int)f.channels
: AFMT_S16_LE;
if (copy_to_user(argp, &val, sizeof(val)) < 0)
return -EFAULT;
return 0;
}
case SNDCTL_DSP_SYNC:
audio_drain();
return 0;
case SNDCTL_DSP_RESET:
audio_stop();
return 0;
case SNDCTL_DSP_GETOSPACE: {
struct audio_buf_info bi;
int space = o->space ? o->space(d) : 0;
bi.fragsize = 4096;
bi.bytes = space;
bi.fragments = space / bi.fragsize;
bi.fragstotal = 0;
if (copy_to_user(argp, &bi, sizeof(bi)) < 0)
return -EFAULT;
return 0;
}
}
return -ENOTTY;
}

View file

@ -211,4 +211,8 @@ struct devsw devsw[] = {
// reverse of major 9: that hands a device to a process, this lets a process // reverse of major 9: that hands a device to a process, this lets a process
// be one. See include/netuser.h. // be one. See include/netuser.h.
[11] = { netuser_read, netuser_write, netuser_ready }, [11] = { netuser_read, netuser_write, netuser_ready },
// /dev/dsp (major 12) -- UNIX audio, kernel/dev/audio.c. Write PCM to it.
// The close hook plays out what is still queued: a program that writes a
// sound and exits expects to hear the end of it.
[12] = { dspread, dspwrite, 0, dspclose },
}; };

View file

@ -40,6 +40,7 @@ static const char *class_names[DEVCLASS_NCLASS] = {
[DEVCLASS_STORAGE] = "storage", [DEVCLASS_STORAGE] = "storage",
[DEVCLASS_NET] = "net", [DEVCLASS_NET] = "net",
[DEVCLASS_BUS] = "bus", [DEVCLASS_BUS] = "bus",
[DEVCLASS_AUDIO] = "audio",
}; };
const char * const char *

View file

@ -365,6 +365,7 @@ pci_class_of(const struct pci_dev *d)
case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE; case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE;
case PCI_CLASS_NETWORK: return DEVCLASS_NET; case PCI_CLASS_NETWORK: return DEVCLASS_NET;
case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS; case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS;
case PCI_CLASS_MULTIMEDIA: return DEVCLASS_AUDIO;
default: return DEVCLASS_OTHER; default: return DEVCLASS_OTHER;
} }
} }

413
kernel/drivers/audio/ac97.c Normal file
View file

@ -0,0 +1,413 @@
// AC'97, the audio class's first driver.
//
// Written from the Intel 82801 datasheet's audio chapter. It is the simplest
// PC audio device that exists on real machines and in QEMU both, which is why
// it is first: the point of this driver is to prove the class and /dev/dsp
// with the least chip-specific code possible. Intel HDA -- which is what a
// laptop built this century actually has -- is a much larger driver, and
// writing it against an interface nothing had ever played a sound through
// would be writing two unproven things at once.
//
// Two register files, both **I/O ports** rather than MMIO:
//
// NAM BAR0, the mixer. Volume, and the codec's own capabilities.
// NABM BAR1, bus mastering. The descriptor list, and the DMA engine that
// walks it.
//
// The DMA engine reads a *buffer descriptor list*: 32 entries, each naming a
// physical address and a length in samples. It plays entry CIV, and stops when
// it reaches LVI -- the "last valid index". Playing continuously is therefore
// not a matter of starting it and walking away; it is a matter of keeping LVI
// ahead of CIV, which is what queue() does.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "pci.h"
#include "device.h"
#include "audio.h"
#include "dma.h"
#include "irq.h"
// --- NAM, the mixer ---------------------------------------------------------
#define NAM_RESET 0x00
#define NAM_MASTER_VOL 0x02
#define NAM_PCM_OUT_VOL 0x18
#define NAM_EXT_ID 0x28
#define NAM_EXT_CTRL 0x2A
#define NAM_PCM_FRONT_RATE 0x2C
#define EXT_VRA 0x0001 // variable rate: the codec can do 44100
// --- NABM, the bus master ---------------------------------------------------
//
// The PCM *out* box starts at 0x10. There are three boxes (in, out, mic) and
// they have the same shape, which is why the offsets below are written as a
// base plus a field rather than as constants.
#define PO_BASE 0x10
#define PO_BDBAR (PO_BASE + 0x00) // uint32: physical address of the list
#define PO_CIV (PO_BASE + 0x04) // uint8: entry being played now
#define PO_LVI (PO_BASE + 0x05) // uint8: last entry the engine may play
#define PO_SR (PO_BASE + 0x06) // uint16: status
#define PO_PICB (PO_BASE + 0x08) // uint16: samples left in this entry
#define PO_CR (PO_BASE + 0x0B) // uint8: control
#define CR_RPBM 0x01 // run
#define CR_RR 0x02 // reset the box
#define CR_IOCE 0x10 // interrupt on completion
#define SR_DCH 0x01 // DMA halted
#define SR_BCIS 0x08 // buffer completion interrupt
#define SR_LVBCI 0x04 // last valid buffer played
#define GLOB_CTRL 0x2C
#define GLOB_STA 0x30
// --- the ring ---------------------------------------------------------------
//
// 32 descriptors of 4 KB each: 128 KB, which at 48 kHz stereo is about
// two thirds of a second. Long enough that a writer can fall behind for a
// scheduling quantum without a gap, short enough that stopping is prompt.
#define NDESC 32
#define DESC_BYTES 4096
struct bdl_entry {
uint32 addr;
uint16 samples; // *samples*, not bytes: 16-bit words, so bytes/2
uint16 flags;
};
#define BDL_IOC 0x8000 // interrupt when this one finishes
struct ac97 {
uint16 nam, nabm; // I/O port bases
struct bdl_entry *bdl; // NDESC entries, DMA memory
uint8 *buf; // NDESC * DESC_BYTES, DMA memory
uint32 bdl_phys, buf_phys;
int head; // next descriptor to fill
int tail; // oldest one still queued
int filled[NDESC]; // bytes in each, 0 when free
int running;
struct spinlock lock;
struct pci_dev *pdev;
};
static struct ac97 ac97dev;
static struct ac97 *
of(struct device *d)
{
(void)d;
return ac97dev.nabm ? &ac97dev : 0;
}
// --- mixer ------------------------------------------------------------------
// Volume is attenuation: 0 is loudest, and the mute bit is 0x8000. Writing 0
// rather than a "sensible" level on purpose -- the kernel has no business
// deciding how loud a machine is, and a device that comes up silent reads as
// broken to whoever is testing it.
static void
mixer_init(struct ac97 *a)
{
outw(a->nam + NAM_RESET, 0);
outw(a->nam + NAM_MASTER_VOL, 0x0000);
outw(a->nam + NAM_PCM_OUT_VOL, 0x0000);
}
// --- the class ops ----------------------------------------------------------
static int
ac97_configure(struct device *d, struct audio_format *f)
{
struct ac97 *a = of(d);
uint16 ext;
if (!a)
return -1;
// Two channels always. AC'97 plays stereo; a mono stream would have to be
// doubled, and doing that in the driver would hide it from the caller that
// asked -- which is what the write-back in the ioctl is for.
f->channels = 2;
f->bits = AUDIO_FMT_S16LE;
ext = inw(a->nam + NAM_EXT_ID);
if (ext & EXT_VRA) {
outw(a->nam + NAM_EXT_CTRL, inw(a->nam + NAM_EXT_CTRL) | EXT_VRA);
outw(a->nam + NAM_PCM_FRONT_RATE, (uint16)f->rate);
// What the codec settled on, which is not always what was asked: it is
// entitled to round to something it can generate.
f->rate = inw(a->nam + NAM_PCM_FRONT_RATE);
} else {
// No variable rate. 48 kHz is the only thing this codec plays, and saying
// so is more useful than accepting 44100 and playing it 9% fast.
f->rate = 48000;
}
return 0;
}
// How many descriptors the engine has finished with.
// Descriptors with nothing in them, counted forward from where the next write
// would go. Stops at the first one still holding audio, which is what makes
// this the number of bytes a write can take without waiting.
static int
free_descs(struct ac97 *a)
{
int n = 0;
for (int i = 0; i < NDESC; i++) {
if (a->filled[(a->head + i) % NDESC])
break;
n++;
}
return n;
}
static int
ac97_space(struct device *d)
{
struct ac97 *a = of(d);
int n;
if (!a)
return 0;
acquire(&a->lock);
n = free_descs(a) * DESC_BYTES;
release(&a->lock);
return n;
}
static int
ac97_queue(struct device *d, const void *pcm, int bytes)
{
struct ac97 *a = of(d);
int civ, idx, take;
if (!a)
return -1;
acquire(&a->lock);
civ = inb(a->nabm + PO_CIV) % NDESC;
idx = a->head;
// Retire what the engine has finished with.
//
// `tail` is the oldest descriptor we still believe is queued; CIV is the one
// being played. Everything between them is done. Doing this here as well as
// in the interrupt handler is deliberate -- the handler is an optimisation,
// and audio that only works when its interrupt arrives is audio that stops
// dead on a machine whose line is misrouted.
while (a->tail != civ && a->filled[a->tail]) {
a->filled[a->tail] = 0;
a->tail = (a->tail + 1) % NDESC;
}
if (a->filled[idx]) {
release(&a->lock);
return 0; // ring full
}
take = bytes > DESC_BYTES ? DESC_BYTES : bytes;
memmove(a->buf + (uint64)idx * DESC_BYTES, pcm, take);
a->bdl[idx].addr = a->buf_phys + (uint32)idx * DESC_BYTES;
a->bdl[idx].samples = (uint16)(take / 2); // 16-bit words
a->bdl[idx].flags = BDL_IOC;
a->filled[idx] = take;
a->head = (idx + 1) % NDESC;
// LVI is the last entry the engine may play. Keeping it at the descriptor
// just written is what makes this a queue rather than a one-shot.
outb(a->nabm + PO_LVI, (uint8)idx);
release(&a->lock);
return take;
}
static void
ac97_start(struct device *d)
{
struct ac97 *a = of(d);
if (!a)
return;
acquire(&a->lock);
if (!a->running) {
outb(a->nabm + PO_CR, CR_RPBM | CR_IOCE);
a->running = 1;
}
release(&a->lock);
}
static void
ac97_stop(struct device *d)
{
struct ac97 *a = of(d);
if (!a)
return;
acquire(&a->lock);
outb(a->nabm + PO_CR, 0);
outb(a->nabm + PO_CR, CR_RR); // reset the box: CIV, LVI, PICB to 0
for (int i = 0; i < NDESC; i++)
a->filled[i] = 0;
a->head = 0;
a->tail = 0;
a->running = 0;
release(&a->lock);
}
static int
ac97_pending(struct device *d)
{
struct ac97 *a = of(d);
int n = 0;
if (!a)
return 0;
acquire(&a->lock);
if (a->running && !(inw(a->nabm + PO_SR) & SR_DCH)) {
for (int i = 0; i < NDESC; i++)
if (a->filled[i])
n += a->filled[i];
}
release(&a->lock);
return n;
}
static const struct audio_ops ac97_ops = {
.configure = ac97_configure,
.space = ac97_space,
.queue = ac97_queue,
.start = ac97_start,
.stop = ac97_stop,
.pending = ac97_pending,
};
// --- interrupt --------------------------------------------------------------
static void
ac97_intr(void *arg)
{
struct ac97 *a = arg;
uint16 sr;
if (!a || !a->nabm)
return;
sr = inw(a->nabm + PO_SR);
// Acknowledge by writing the bits back. A status bit left set on a
// level-triggered line is an interrupt that re-fires for ever -- the same
// shape as the e1000 bug in docs/history/kernel.md section 13.
if (sr & (SR_BCIS | SR_LVBCI | SR_DCH))
outw(a->nabm + PO_SR, sr & (SR_BCIS | SR_LVBCI | SR_DCH));
{
int civ = inb(a->nabm + PO_CIV) % NDESC;
// Everything the engine has passed is free again. Through `tail` rather
// than by clearing one entry, so this and ac97_queue() cannot disagree
// about which descriptors are in flight.
while (a->tail != civ && a->filled[a->tail]) {
a->filled[a->tail] = 0;
a->tail = (a->tail + 1) % NDESC;
}
}
// The writer is asleep on the tick rather than on this device, so there is
// nothing to wake -- see audio_wait(). Keeping the handler to "acknowledge
// and account" is deliberate: an interrupt handler that can be deleted
// without the audio stopping is one that cannot deadlock the audio either.
}
// --- probe ------------------------------------------------------------------
static int
ac97_probe(struct pci_dev *d)
{
struct ac97 *a = &ac97dev;
if (a->nabm)
return -1; // one is enough
// Both BARs are I/O ports on this device, which is why nothing here maps
// anything: `is_io` is what says so, and a driver that ioremapped these
// would be mapping a port number as if it were an address.
if (!d->bar[0].is_io || !d->bar[1].is_io ||
!d->bar[0].base || !d->bar[1].base) {
cprintf("ac97: expected two I/O BARs; not driving this device\n");
return -1;
}
a->nam = (uint16)d->bar[0].base;
a->nabm = (uint16)d->bar[1].base;
a->pdev = d;
initlock(&a->lock, "ac97");
pci_enable(d, PCI_CMD_IO | PCI_CMD_BUS_MASTER);
// The descriptor list and the audio itself, both from the DMA arena: the
// engine reads them by physical address, and kalloc's pages are adjacent
// only by chance.
a->bdl = dma_alloc(NDESC * sizeof(struct bdl_entry), 4096);
a->buf = dma_alloc(NDESC * DESC_BYTES, 4096);
if (!a->bdl || !a->buf) {
cprintf("ac97: no DMA memory for a %d KB ring\n",
(NDESC * DESC_BYTES) / 1024);
a->nabm = 0;
return -1;
}
a->bdl_phys = (uint32)dma_phys(a->bdl);
a->buf_phys = (uint32)dma_phys(a->buf);
memset(a->bdl, 0, NDESC * sizeof(struct bdl_entry));
memset(a->buf, 0, NDESC * DESC_BYTES);
// Cold reset of the whole controller, then the PCM-out box.
outl(a->nabm + GLOB_CTRL, 0x00000002);
mixer_init(a);
outb(a->nabm + PO_CR, CR_RR);
for (int spin = 0; spin < 100000 && (inb(a->nabm + PO_CR) & CR_RR); spin++)
;
outl(a->nabm + PO_BDBAR, a->bdl_phys);
if (d->irq != 0xFF && d->irq < 24)
irq_register(d->irq, ac97_intr, a, "ac97", 1);
kinfo("ac97: nam %x nabm %x, %d KB ring, irq %d\n",
a->nam, a->nabm, (NDESC * DESC_BYTES) / 1024, d->irq);
return 0;
}
static const struct pci_match ac97_match[] = {
{ 0x8086, 0x2415, 0, 0 }, // 82801AA, which is what QEMU's -device AC97 is
{ 0x8086, 0x2425, 0, 0 }, // 82801AB
{ 0x8086, 0x2445, 0, 0 }, // 82801BA
{ 0x8086, 0x24C5, 0, 0 }, // ICH4
{ 0x8086, 0x24D5, 0, 0 }, // ICH5
{ 0x8086, 0x266E, 0, 0 }, // ICH6
{ 0, 0, 0, 0 },
};
const struct pci_driver ac97_driver = {
.name = "ac97",
.devname = "audio",
.match = ac97_match,
.ops = &ac97_ops,
.probe = ac97_probe,
};

View file

@ -32,6 +32,7 @@ extern const struct pci_driver vga_pci_driver;
extern const struct pci_driver xhci_driver; extern const struct pci_driver xhci_driver;
extern const struct pci_driver virtio_gpu_driver; extern const struct pci_driver virtio_gpu_driver;
extern const struct pci_driver e1000_driver; extern const struct pci_driver e1000_driver;
extern const struct pci_driver ac97_driver;
// Order matters here in one respect: virtio-gpu is offered the bus before the // Order matters here in one respect: virtio-gpu is offered the bus before the
// VBE framebuffer driver, so that on a machine with both, the device with real // VBE framebuffer driver, so that on a machine with both, the device with real
@ -43,6 +44,7 @@ static const struct pci_driver *drivers[] = {
&vga_pci_driver, &vga_pci_driver,
&e1000_driver, &e1000_driver,
&xhci_driver, &xhci_driver,
&ac97_driver,
}; };
void void

View file

@ -96,6 +96,8 @@ static const struct devnode devnodes[] = {
// include/dbg.h and cmd/schdbgd. // include/dbg.h and cmd/schdbgd.
{ "dbgro", 10, 1, D_ROOT }, { "dbgro", 10, 1, D_ROOT },
{ "netdev", 11, 0, D_ROOT }, { "netdev", 11, 0, D_ROOT },
// Audio, spelled the way UNIX spells it. See include/audio.h.
{ "dsp", 12, 0, D_ROOT },
{ "mouse", 3, 1, D_ROOT }, { "mouse", 3, 1, D_ROOT },
// Raw disks. Created whether or not a drive is present: opening one with no // Raw disks. Created whether or not a drive is present: opening one with no

View file

@ -1286,6 +1286,8 @@ addr_t sys_ioctl(void)
return pciuser_ioctl(request, argp); return pciuser_ioctl(request, argp);
if (is_dev && f->ip->major == NETUSER_MAJOR) if (is_dev && f->ip->major == NETUSER_MAJOR)
return netuser_ioctl(request, argp); return netuser_ioctl(request, argp);
if (is_dev && f->ip->major == AUDIO_MAJOR)
return dsp_ioctl((int)request, argp);
int is_tty = (is_dev && f->ip->major == 1); int is_tty = (is_dev && f->ip->major == 1);
if (!is_tty) { if (!is_tty) {