Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
284 lines
7.4 KiB
C
284 lines
7.4 KiB
C
// AudioEngine: the audio class, and /dev/dsp above it.
|
|
//
|
|
// See include/dev/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 "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "dev/device.h"
|
|
#include "dev/audio.h"
|
|
#include "core/proc.h"
|
|
#include "core/uaccess.h"
|
|
#include "uapi/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;
|
|
}
|