diff --git a/Makefile b/Makefile index 8ca122c..78929e0 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ KERN_CORE = $(addprefix $(BUILD)/kernel/core/, \ 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 \ 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 # 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/virtio_gpu/virtio_gpu.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/usb/core/usb.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/. # ----------------------------------------------------------------------- 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)) # ----------------------------------------------------------------------- @@ -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. 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. # # 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) 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 # "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) 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 qemu: esp $(BUILD)/schfs.img $(OVMF_CODE) $(OVMF_VARS) diff --git a/cmd/tone/main.c b/cmd/tone/main.c new file mode 100644 index 0000000..a0207c0 --- /dev/null +++ b/cmd/tone/main.c @@ -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; +} diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 81b643b..3c00cb9 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -92,6 +92,7 @@ Read in this order if you are new: | [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. | | [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. | | [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`. | diff --git a/docs/architecture/audio.md b/docs/architecture/audio.md new file mode 100644 index 0000000..222cd1e --- /dev/null +++ b/docs/architecture/audio.md @@ -0,0 +1,124 @@ + +# 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. diff --git a/include/audio.h b/include/audio.h new file mode 100644 index 0000000..9bc062f --- /dev/null +++ b/include/audio.h @@ -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 +}; diff --git a/include/defs.h b/include/defs.h index 50742b4..8a0461d 100644 --- a/include/defs.h +++ b/include/defs.h @@ -611,6 +611,13 @@ void fbclose(struct inode*); // further syscalls, so it is the last the kernel hears about it. 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. void display_panic_present(void); // Push the console's newly-drawn rows to a second display, if the kernel diff --git a/include/device.h b/include/device.h index 2f765ac..6d90493 100644 --- a/include/device.h +++ b/include/device.h @@ -46,6 +46,7 @@ enum dev_class { DEVCLASS_STORAGE, DEVCLASS_NET, DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs) + DEVCLASS_AUDIO, DEVCLASS_NCLASS }; diff --git a/include/file.h b/include/file.h index e379e2a..49357cf 100644 --- a/include/file.h +++ b/include/file.h @@ -187,3 +187,5 @@ extern struct devsw devsw[]; #define CONSOLE 1 // Major number of /dev/input/eventN. The minor is the device index. #define INPUT_MAJOR 7 +// Major number of /dev/dsp. See include/audio.h. +#define AUDIO_MAJOR 12 diff --git a/include/param.h b/include/param.h index 7680782..eafbef4 100644 --- a/include/param.h +++ b/include/param.h @@ -29,7 +29,7 @@ #define NOFILE 128 // open files per process #define NFILE 512 // open files per system #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 USBDISK_DEV 3 // USB mass storage (kernel/drivers/usb_storage.c) diff --git a/include/x86.h b/include/x86.h index 5f40f3c..1a9668f 100644 --- a/include/x86.h +++ b/include/x86.h @@ -40,6 +40,17 @@ outb(ushort port, uchar data) 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 inl(ushort port) { diff --git a/kernel/dev/audio.c b/kernel/dev/audio.c new file mode 100644 index 0000000..3df89a2 --- /dev/null +++ b/kernel/dev/audio.c @@ -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; +} diff --git a/kernel/dev/cdev.c b/kernel/dev/cdev.c index a557b4b..afe2edf 100644 --- a/kernel/dev/cdev.c +++ b/kernel/dev/cdev.c @@ -211,4 +211,8 @@ struct devsw devsw[] = { // reverse of major 9: that hands a device to a process, this lets a process // be one. See include/netuser.h. [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 }, }; diff --git a/kernel/dev/device.c b/kernel/dev/device.c index 648418c..53a7b45 100644 --- a/kernel/dev/device.c +++ b/kernel/dev/device.c @@ -40,6 +40,7 @@ static const char *class_names[DEVCLASS_NCLASS] = { [DEVCLASS_STORAGE] = "storage", [DEVCLASS_NET] = "net", [DEVCLASS_BUS] = "bus", + [DEVCLASS_AUDIO] = "audio", }; const char * diff --git a/kernel/dev/pci.c b/kernel/dev/pci.c index 7bf35b8..55721b0 100644 --- a/kernel/dev/pci.c +++ b/kernel/dev/pci.c @@ -365,6 +365,7 @@ pci_class_of(const struct pci_dev *d) case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE; case PCI_CLASS_NETWORK: return DEVCLASS_NET; case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS; + case PCI_CLASS_MULTIMEDIA: return DEVCLASS_AUDIO; default: return DEVCLASS_OTHER; } } diff --git a/kernel/drivers/audio/ac97.c b/kernel/drivers/audio/ac97.c new file mode 100644 index 0000000..2afe181 --- /dev/null +++ b/kernel/drivers/audio/ac97.c @@ -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, +}; diff --git a/kernel/drivers/registry.c b/kernel/drivers/registry.c index e7b0a9e..34653e0 100644 --- a/kernel/drivers/registry.c +++ b/kernel/drivers/registry.c @@ -32,6 +32,7 @@ extern const struct pci_driver vga_pci_driver; extern const struct pci_driver xhci_driver; extern const struct pci_driver virtio_gpu_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 // 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, &e1000_driver, &xhci_driver, + &ac97_driver, }; void diff --git a/kernel/fs/devfs.c b/kernel/fs/devfs.c index dca42e6..9520548 100644 --- a/kernel/fs/devfs.c +++ b/kernel/fs/devfs.c @@ -96,6 +96,8 @@ static const struct devnode devnodes[] = { // include/dbg.h and cmd/schdbgd. { "dbgro", 10, 1, 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 }, // Raw disks. Created whether or not a drive is present: opening one with no diff --git a/kernel/sys/sys_fs.c b/kernel/sys/sys_fs.c index 2ffe86a..6e3f0ca 100644 --- a/kernel/sys/sys_fs.c +++ b/kernel/sys/sys_fs.c @@ -1286,6 +1286,8 @@ addr_t sys_ioctl(void) return pciuser_ioctl(request, argp); if (is_dev && f->ip->major == NETUSER_MAJOR) 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); if (!is_tty) {