From c77841ce3d7acf4bc06ed48121316ce62d284e32 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 05:39:16 +0100 Subject: [PATCH] Sound through ALSA, a 2D game, and a stack walk that cannot panic SDL 3 has no OSS backend and SchOS has no ALSA kernel side, so sound now goes SDL -> alsa-lib -> alsa-plugins' OSS plugin -> /dev/dsp. That path found two bugs underneath it: - ac97: resetting the PCM-out box also clears the descriptor list address, and nothing set it again. Every stream after the first stop played a "list" at physical address 0 -- silence -- and ALSA resets before each stream. If the engine halts at the end of the queue, the run bit is set again. - /dev/dsp: SETFRAGMENT, GETFMTS, GETCAPS, SETTRIGGER, GETOPTR and GETODELAY for the plugin's ring-and-pointer model, and poll(), writable once a whole fragment fits (ALSA spun at 76% CPU otherwise). speaker-test now plays in real time at 0% CPU, verified in a recording of QEMU's AC'97. - /etc/asound.conf: the default device is a plug to the OSS device at 16-bit stereo 48 kHz, what every SchOS audio driver plays. Also: - getstackpcs: each frame link must go up the same stack by less than KSTACKSIZE. The chain ends at the syscall entry with the user's rbp, which lbreakouthd uses as a register (-1024); the walk dereferenced it under the syscall tracer and the kernel panicked inside acquire(). - fb: FBIOGET_VSCREENINFO reports nominal timings, as Linux's efifb does, instead of zeros, which Xorg's fbdev turned into a 0 Hz RandR mode. - procdump (Ctrl-P) shows where runnable threads are in user space. - World: lbreakouthd instead of lbreakout2 (its stk.c passes vsnprintf a size of 511 plus the prefix length, and Alpine's fortified headers trap on that), plus curl, alsa-lib and alsa-plugins. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4 --- include/core/defs.h | 1 + include/dev/audio.h | 18 ++++++ kernel/core/spinlock.c | 14 ++++- kernel/dev/audio.c | 119 +++++++++++++++++++++++++++++++++++- kernel/dev/fb.c | 21 ++++++- kernel/drivers/audio/ac97.c | 13 ++++ kernel/fs/file.c | 5 ++ kernel/proc/proc.c | 4 ++ ports/alpine/world | 15 ++++- ports/alpine/world.lock | 13 +++- tools/posix/mkaroot.sh | 26 ++++++++ 11 files changed, 237 insertions(+), 12 deletions(-) diff --git a/include/core/defs.h b/include/core/defs.h index 0cc078f..ab77726 100644 --- a/include/core/defs.h +++ b/include/core/defs.h @@ -798,6 +798,7 @@ char* shm_page(int id, int pg); uint64 memfd_size(int id); int memfd_truncate(int id, uint64 len); int memfd_seals(int id, int add); +int dsp_writable(void); int memfd_rw(int id, int user, uint64 buf, uint64 off, int n, int writing); void shm_detach(int id); diff --git a/include/dev/audio.h b/include/dev/audio.h index 9bc062f..caa0a49 100644 --- a/include/dev/audio.h +++ b/include/dev/audio.h @@ -79,6 +79,7 @@ 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); +int dsp_writable(void); // poll(): room for a write // --- what userspace uses --------------------------------------------------- // @@ -92,6 +93,23 @@ void audio_stop(void); #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 +// The rest of what a buffer-and-pointer client needs -- ALSA's OSS plugin, which +// is how SDL and everything else that speaks ALSA reaches this device. +#define SNDCTL_DSP_SETFRAGMENT 0xC004500A // int in/out: count<<16 | log2(size) +#define SNDCTL_DSP_GETFMTS 0x8004500B // int out: AFMT_* mask +#define SNDCTL_DSP_GETCAPS 0x8004500F // int out: DSP_CAP_* +#define SNDCTL_DSP_SETTRIGGER 0x40045010 // int in: PCM_ENABLE_OUTPUT or 0 +#define SNDCTL_DSP_GETOPTR 0x800C5012 // struct count_info out +#define SNDCTL_DSP_GETODELAY 0x80045017 // int out: bytes not yet heard + +#define DSP_CAP_TRIGGER 0x00001000 +#define PCM_ENABLE_OUTPUT 0x00000002 + +struct count_info { + int bytes; // played since the last reset + int blocks; // fragments played since the last GETOPTR + int ptr; // where playback is in a ring of fragsize * fragments +}; #define AFMT_S16_LE 0x00000010 diff --git a/kernel/core/spinlock.c b/kernel/core/spinlock.c index 4484054..2eda2d5 100644 --- a/kernel/core/spinlock.c +++ b/kernel/core/spinlock.c @@ -90,7 +90,19 @@ getstackpcs(addr_t *rbp, addr_t pcs[]) if(((addr_t)rbp & 7) != 0) break; pcs[i] = rbp[1]; - rbp = (addr_t*)rbp[0]; + // The next frame is the caller's, further up the same stack: higher, and + // by less than a whole kernel stack. The chain ends at the system call or + // interrupt entry, and the rbp saved there is the user's -- which an + // optimised program uses as an ordinary register. lbreakouthd left -1024 + // in it, 0xfffffffffffffc00, which passed every test above and faulted + // here under the syscall tracer, taking the kernel down from inside the + // lock it was recording. + addr_t *next = (addr_t*)rbp[0]; + if(next <= rbp || (addr_t)next - (addr_t)rbp >= KSTACKSIZE){ + i++; // keep the return address just read + break; + } + rbp = next; } for(; i < 10; i++) pcs[i] = 0; diff --git a/kernel/dev/audio.c b/kernel/dev/audio.c index 9fa174a..02a151c 100644 --- a/kernel/dev/audio.c +++ b/kernel/dev/audio.c @@ -26,6 +26,16 @@ static int audio_lock_init; // to be told once and then have it stay told. static struct audio_format current_fmt = { 48000, 2, AUDIO_FMT_S16LE }; +// The OSS view of the stream, for clients that track a play pointer in a ring +// rather than just writing: bytes accepted by the driver since boot, where the +// last reset left the count, the ring geometry SETFRAGMENT asked for, and the +// played count GETOPTR last reported (for its fragment count). +static uint64 queued_total; +static uint64 reset_base; +static uint64 last_optr; +static int frag_size = 4096; +static int frag_count = 16; + static void lock_once(void) { @@ -114,6 +124,10 @@ audio_write(const void *pcm, int bytes) } done += n; + lock_once(); + acquire(&audio_lock); + queued_total += (uint64)n; + release(&audio_lock); } if (o->start) @@ -211,6 +225,45 @@ dspclose(struct inode *ip) audio_stop(); } +// Bytes the speaker has played since boot: everything queued, less what is +// still waiting. A reset discards the waiting part, which then counts as played +// -- the count only goes forward, and GETOPTR measures from the reset. +static uint64 +played_total(struct device *d, const struct audio_ops *o) +{ + uint64 q, p; + int pend = o->pending ? o->pending(d) : 0; + + lock_once(); + acquire(&audio_lock); + q = queued_total; + release(&audio_lock); + p = (pend > 0 && (uint64)pend <= q) ? q - (uint64)pend : q; + return p; +} + +// Writable once a whole fragment fits, as OSS defines it. ALSA's plugin sets +// the fragment to its period and waits in poll() for one to be free; reporting +// writable at the first free byte had it wake, find less than a period of +// room, and poll again -- 76% of a CPU to play a sine wave. +int +dsp_writable(void) +{ + struct device *d = audio_get(); + const struct audio_ops *o = ops_of(d); + int want; + + if (!o || !o->space) + return 0; + lock_once(); + acquire(&audio_lock); + want = frag_size; + release(&audio_lock); + if (want > 65536) + want = 65536; // never more than a driver's ring can free at once + return o->space(d) >= want; +} + int dsp_ioctl(int request, uint64 argp) { @@ -262,18 +315,78 @@ dsp_ioctl(int request, uint64 argp) audio_drain(); return 0; - case SNDCTL_DSP_RESET: + case SNDCTL_DSP_RESET: { audio_stop(); + uint64 p = played_total(d, o); + acquire(&audio_lock); + reset_base = last_optr = p; + release(&audio_lock); return 0; + } + + // The ring the client thinks in. The driver's own ring is whatever it is; + // these only shape the numbers GETOPTR and GETOSPACE report, which is all a + // client does with them. Fragments of 256 bytes to 64 KiB, 2 to 1024 of them. + case SNDCTL_DSP_SETFRAGMENT: { + if (copy_from_user(&val, argp, sizeof(val)) < 0) + return -EFAULT; + int shift = val & 0xffff, count = (val >> 16) & 0xffff; + if (shift < 8) shift = 8; + if (shift > 16) shift = 16; + if (count < 2) count = 2; + if (count > 1024 || count == 0x7fff) count = 1024; + acquire(&audio_lock); + frag_size = 1 << shift; + frag_count = count; + release(&audio_lock); + val = (count << 16) | shift; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + } + + case SNDCTL_DSP_GETFMTS: + val = AFMT_S16_LE; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + + case SNDCTL_DSP_GETCAPS: + val = DSP_CAP_TRIGGER; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; + + // Enabling output starts whatever is queued. Disabling is accepted and does + // nothing: the stream stops by itself when the queue runs dry, and stopping + // it here would throw away what is queued, which a pause must not. + case SNDCTL_DSP_SETTRIGGER: + if (copy_from_user(&val, argp, sizeof(val)) < 0) + return -EFAULT; + if ((val & PCM_ENABLE_OUTPUT) && o->start) + o->start(d); + return 0; + + case SNDCTL_DSP_GETOPTR: { + struct count_info ci; + uint64 p = played_total(d, o); + acquire(&audio_lock); + uint64 since = p - reset_base; + uint64 ring = (uint64)frag_size * (uint64)frag_count; + ci.bytes = (int)since; + ci.blocks = (int)((p - last_optr) / (uint64)frag_size); + ci.ptr = (int)(since % ring); + last_optr = p; + release(&audio_lock); + return copy_to_user(argp, &ci, sizeof(ci)) < 0 ? -EFAULT : 0; + } + + case SNDCTL_DSP_GETODELAY: + val = o->pending ? o->pending(d) : 0; + return copy_to_user(argp, &val, sizeof(val)) < 0 ? -EFAULT : 0; case SNDCTL_DSP_GETOSPACE: { struct audio_buf_info bi; int space = o->space ? o->space(d) : 0; - bi.fragsize = 4096; + bi.fragsize = frag_size; bi.bytes = space; bi.fragments = space / bi.fragsize; - bi.fragstotal = 0; + bi.fragstotal = frag_count; if (copy_to_user(argp, &bi, sizeof(bi)) < 0) return -EFAULT; return 0; diff --git a/kernel/dev/fb.c b/kernel/dev/fb.c index 0a62efd..268f30f 100644 --- a/kernel/dev/fb.c +++ b/kernel/dev/fb.c @@ -147,6 +147,23 @@ fbclose(struct inode *ip) // the largest struct here -- fb_fix_screeninfo is half the size of // fb_var_screeninfo, and a buffer sitting near the end of a mapping is a // legitimate place to put the smaller one. +// Timings for a mode the firmware set and never described. GOP and VBE give a +// size and a pixel format and nothing about the signal, so these are made up -- +// and made up the way Linux's efifb makes them up, which is what every program +// that reads them was written against. They used to be zero, which is not "no +// answer" but the answer "0 Hz": Xorg's fbdev driver built a RandR mode with a +// dot clock of 0 from it, and SDL 3 sized a fullscreen window from that mode +// as 1x1. Physical size stays zero; nothing reads it as a number. +static void +nominal_timings(struct fb_var_screeninfo *v) +{ + if (v->xres == 0 || v->yres == 0) + return; + v->pixclock = 10000000 / v->xres * 1000 / v->yres; + v->left_margin = (v->xres / 8) & 0xf8; + v->hsync_len = (v->xres / 8) & 0xf8; +} + int fb_ioctl(int request, addr_t arg) { @@ -169,8 +186,7 @@ fb_ioctl(int request, addr_t arg) v->bits_per_pixel = gfx_fb_bpp(); v->activate = FB_ACTIVATE_NOW; pixel_format(v, v->bits_per_pixel); - // height/width (physical millimetres) and every timing field stay zero: - // VBE does not report them and a guess would be worse than an admission. + nominal_timings(v); return 0; } @@ -212,6 +228,7 @@ fb_ioctl(int request, addr_t arg) v->bits_per_pixel = gfx_fb_bpp(); v->activate = act; pixel_format(v, v->bits_per_pixel); + nominal_timings(v); return 0; } diff --git a/kernel/drivers/audio/ac97.c b/kernel/drivers/audio/ac97.c index e0c3d01..9371ea9 100644 --- a/kernel/drivers/audio/ac97.c +++ b/kernel/drivers/audio/ac97.c @@ -234,6 +234,12 @@ ac97_queue(struct device *d, const void *pcm, int bytes) // just written is what makes this a queue rather than a one-shot. outb(a->nabm + PO_LVI, (uint8)idx); + // An engine that ran off the end of the queue halts. QEMU resumes it when + // LVI moves; the datasheet asks for the run bit again, so if it is still + // halted after the write above, give it that. + if (a->running && (inw(a->nabm + PO_SR) & SR_DCH)) + outb(a->nabm + PO_CR, CR_RPBM | CR_IOCE); + release(&a->lock); return take; } @@ -263,6 +269,13 @@ ac97_stop(struct device *d) 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 spin = 0; spin < 100000 && (inb(a->nabm + PO_CR) & CR_RR); spin++) + ; + // The reset clears the descriptor list's address too, and the engine then + // walks a "list" at physical address 0: every write after the first stop + // played zeros. ALSA resets the device before each stream, so that was all + // of them. Linux's intel8x0 rewrites it after every reset for this reason. + outl(a->nabm + PO_BDBAR, a->bdl_phys); for (int i = 0; i < NDESC; i++) a->filled[i] = 0; a->head = 0; diff --git a/kernel/fs/file.c b/kernel/fs/file.c index caf7b36..36d915c 100644 --- a/kernel/fs/file.c +++ b/kernel/fs/file.c @@ -427,6 +427,11 @@ file_poll(struct file *f, int events) (f->ip->major == PTM_MAJOR || f->ip->major == PTS_MAJOR)) return pty_poll(f->ip, events, f->readable, f->writable); + // /dev/dsp is writable when the driver has room. ALSA's OSS plugin waits + // in poll() for exactly this between periods. + if(f->ip && f->ip->type == T_DEV && f->ip->major == AUDIO_MAJOR) + return (events & POLLOUT) && f->writable && dsp_writable() ? POLLOUT : 0; + if(f->ip && f->ip->type == T_DEV && f->ip->major == CONSOLE){ int rev = 0; if((events & POLLIN) && f->readable && console_input_ready(f->ip ? f->ip->minor : 1)) diff --git a/kernel/proc/proc.c b/kernel/proc/proc.c index 789908a..ec448f5 100644 --- a/kernel/proc/proc.c +++ b/kernel/proc/proc.c @@ -2794,6 +2794,10 @@ procdump(void) for(i=0; i<10 && pc[i] != 0; i++) cprintf(" %p", pc[i]); } + // Where a thread that is not asleep last was in user space: the question + // when a program spins without making a system call. + if((p->state == RUNNABLE || p->state == RUNNING) && p->tf && p->pgdir) + cprintf(" user rip %p rsp %p", p->tf->rip, p->tf->rsp); cprintf("\n"); } } diff --git a/ports/alpine/world b/ports/alpine/world index 47ce7b3..ae250c5 100644 --- a/ports/alpine/world +++ b/ports/alpine/world @@ -34,16 +34,25 @@ tmux mc ncdu fastfetch -# Games. In the terminal: nethack, the BSD set, and two toys. On X: lbreakout2 -# (SDL 1.2) and the classic X clients. +# Games. In the terminal: nethack, the BSD set, and two toys. On X: +# lbreakouthd (SDL 2) and the classic X clients. Not lbreakout2: its stk.c +# hands vsnprintf a size of 511 + the prefix length, and Alpine's fortified +# headers trap on that before it loads its first image -- on Alpine too. nethack bsd-games cmatrix sl -lbreakout2 +lbreakouthd xeyes xclock xcalc # A browser: NetSurf, on GTK 3 -- glib's threads, cairo, pango, TLS. The step # before WebKit (epiphany), which is `apk add` away. netsurf +# curl's command line; libcurl is already here for NetSurf. +curl +# Sound for everything that speaks ALSA, SDL included: alsa-lib with the OSS +# plugin, pointed at /dev/dsp by schos-base's /etc/asound.conf. SchOS has OSS, +# not ALSA's kernel side, and SDL 3 has no OSS backend of its own. +alsa-lib +alsa-plugins diff --git a/ports/alpine/world.lock b/ports/alpine/world.lock index f165558..2db1fbf 100644 --- a/ports/alpine/world.lock +++ b/ports/alpine/world.lock @@ -1,6 +1,8 @@ # Generated by tools/posix/alpine-lock.sh from world. Do not edit; # change world and run `make alpine-lock`. # file sha256 repository +alsa-lib-1.2.15.3-r0.apk 052de5639df635a03cfae1d7504ed4ed75c4e2d2cfff59efc116bb847c1f4ac4 main +alsa-plugins-1.2.12-r2.apk 0ac6513add2a5268f67231b241473b4b7b43f5aad9f4674c900525e5152e0752 community at-spi2-core-2.60.5-r0.apk c9989ba9b2ff44f87a2106ca9560c2e554a56811d0fc2863b22741d576f1d84f main at-spi2-core-libs-2.60.5-r0.apk bcd4846311c8b19ad7e08ffc3cf726ddbe3d36bbc524386bda64758f5c5981a6 main avahi-libs-0.9_rc4-r0.apk 380d59c58cfce9cb5cf342cf73947749ca77a880f729970e2f508aaa6fc2f713 main @@ -17,6 +19,8 @@ cairo-gobject-1.18.4-r1.apk ec302c75a24edaa4a2f867cfb5eec85cf30efe184044bab6e2b9 c-ares-1.34.9-r0.apk 3539a3e11c1ef1516117d8179a546ac882086e94e0d78ad51d687b3fa2b3b5f2 main cmatrix-2.0-r2.apk d9f6bea3851492a6db2a9cd3f7e2df0f0756c6651a702b36ed2b887328ad1f00 community cups-libs-2.4.19-r1.apk 5739c568d90f23006d74ff82619e1a18447a6f698ae2af61970debe6658a7bea main +curl-8.22.0-r0.apk 9262352e6341bb87e3047d1ca0b4051b2a9f4011409df25e4190e6be0334b860 main +curl-zsh-completion-8.22.0-r0.apk 182a5f0afb5fd8786137f4610c065c305aa593fd18994df908c146335fb6011c main dbus-libs-1.16.2-r2.apk 105f6ba17454c587ca9345f9deb348012d1950588a22da42120c954d396c99b6 main e2fsprogs-libs-1.47.4-r0.apk 2e067395a6981b06cfd2cd3abf0e6c6574f7ef64d02e01f75fc5323d4a337fb9 main encodings-1.1.0-r0.apk f7c0eb762638d0249b4fbbe07a8e4ed6aab8e86b74c239c40e47a36219777d52 main @@ -44,7 +48,8 @@ harfbuzz-13.2.1-r0.apk 01f7862265ef2256bbadd0fc998df3d1243dee606df3bd8284471cf39 htop-3.5.3-r0.apk 6f6d444ca9a4133c2c13323ded04128783d59f924bcff0276d150a982904a2f3 main hwdata-pci-0.408-r0.apk fe61404e6d2bd1e04c021d53641c0ebce97911084d77994e43a288a1278686c1 main hwdata-usb-0.408-r0.apk 272d365cb05dd085dd8022154cf71e3f3b0aaddd41c1269eeaf939c9d38675c8 main -lbreakout2-2.6.5-r2.apk 2c0f448af62e8138d9e612a858198f4bb906af33db50a32bb4bd468239521449 community +lbreakouthd-1.2.2-r0.apk 4d7f78d866a7c544132e5f159d18f978515ac948517f2b8a6c625756bb81a3e0 community +lbreakouthd-data-1.2.2-r0.apk 28ce98df27bfbb442fa397fbb9eb3eb5464e5b7b040ea034f207be52f4f4f07f community lcms2-2.19-r0.apk b4cb038af3c2f02bedd8456ae876c97ac0a9f29789add77871bfd6ce084ff136 main libatk-1.0-2.60.5-r0.apk ab5f603b17f0f0c5577d259b8263c5cbc6c7b3fd031f06ddc1e3e26417e1609a main libatk-bridge-2.0-2.60.5-r0.apk f5e38bc54bae780680134f7b96ce8e01ef24e98ae0ac67ded7229af07aa0b0f1 main @@ -78,6 +83,7 @@ libpciaccess-0.19-r0.apk 03fdccd1e2a0825e7f7fabbac67212e05d271d31d2eff44cc92f707 libpng-1.6.59-r0.apk d16aeb0597f2bc43e0583ac9ff779388ec160c3e22568b640122e94ba55be6d7 main libpsl-0.21.5-r3.apk ec665a344b6e887f00f0a47015cc732c3a281b69e84641dc8662ea234fde6cdb main librsvg-2.62.3-r0.apk e2aeb4a2b6b1ab20627dbb0e9136ea90f7120ec61412ed52f4335ee231361fde community +libsamplerate-0.2.2-r3.apk d47765972e0f4b7c5585a5b27957ffe9bc63d4cc9729134fd876ee5d3f5f986d main libseccomp-2.6.1-r0.apk b22ccfe8cb072e4c8d3bc17222eeda132e270ce6258c27622acb193bf27b48e1 main libsharpyuv-1.6.0-r0.apk 34621ff53f43030d24d1224fe9cb53b1590ab614cbce6080005cde703e6c874a main libsm-1.2.6-r0.apk dd7eff58a46fd60e4afee9c7c1c3508920b0e1d6560129e59aee82329f68afc8 main @@ -138,10 +144,11 @@ pango-1.57.1-r0.apk c616b62df908026ef413b338b9043e36706b05b6d035049394e5d14022e6 pcre2-10.49-r0.apk 2919897914820f42a17a8d7d37df310d2dfad1c82ebe5f539cc5c765f916752a main pixman-0.46.4-r0.apk 378550cbe348dda9b0733014375dba87a89f0077c5f3dbeba7dce8dbcdda707e main pkgconf-2.5.1-r0.apk 20cc47b76b82e9424a35ecc4fdd7bd904ef49ada1684214910fae31bc0cab737 main -sdl12-compat-1.2.76-r1.apk 124791270d613d1e33115625b95725487fe76ccfbc08638b8baa1b180287deb9 community sdl2-compat-2.32.68-r0.apk 0c4c1279446cba3c2065a3d3c2cacfa9eb4aeff507757c5a37193aaf028c5cf7 community +sdl2_image-2.8.12-r0.apk 9d9f337de7fe25c363700701b8dd76ff4026de9c3d977e321ada08058b57eeda community +sdl2_mixer-2.8.2-r0.apk 504b68ae9cf6705942853bb6977d3b341615c72066031e5aff54d0c6aca76964 community +sdl2_ttf-2.24.0-r0.apk 0efcd7f03a1c70fc87706b0bb1e95b8747a1b796f089af56f296bf764ac8d988 community sdl3-3.4.10-r0.apk e9e4537d455f8b710a1f6fed7607c3cdb492fdc155684cf38e1b054bba809427 community -sdl_mixer-1.2.12-r3.apk 9733a4e906b76f8bb5f246578ffc123a39a489fbfe60212abbaaaa0782e62e1c community shared-mime-info-2.4-r7.apk b0ed78b502cacfa2dfb6ff87aaef36a034258026873ab5b73f5e603d6623a147 main sl-5.02-r1.apk aa3ceb845953904ab61459f0f97416903c56a80b428f8bc320d879b9e58c1a57 community slang-2.3.3-r3.apk 96daf8e02cbcc0d4fd7163d087ae2a71a1aea279a0ac8d2a00a1612ccdccf644 main diff --git a/tools/posix/mkaroot.sh b/tools/posix/mkaroot.sh index be6ca0e..04aa13a 100644 --- a/tools/posix/mkaroot.sh +++ b/tools/posix/mkaroot.sh @@ -211,6 +211,32 @@ exec "$@" BEOF chmod 755 "$P/usr/bin/bwrap" +# ALSA's default device, through the OSS plugin to /dev/dsp. The plug in front +# converts whatever a program plays to what every SchOS audio driver accepts: +# 16-bit stereo at 48 kHz (AC'97 without variable rate plays nothing else, and +# the plugin refuses a rate the device rounds by more than 1%). +mkdir -p "$P/etc" +cat > "$P/etc/asound.conf" <<'AEOF' +# SchOS: ALSA plays through OSS's /dev/dsp (kernel/dev/audio.c). +pcm.!default { + type plug + slave { + pcm "dsp" + format S16_LE + channels 2 + rate 48000 + } +} +pcm.dsp { + type oss + device /dev/dsp +} +ctl.!default { + type hw + card 0 +} +AEOF + # Mount points and runtime directories are alpine-baselayout's; an empty # directory here only duplicates it. for d in dev proc sys run tmp srv; do