schdbgd ships Disabled, and its manifest explains why: a debugger that can read
the process table should not run on a machine whose owner never asked for one.
That default is right and has one bad consequence -- **the machines that most
need debugging are the ones you cannot get a shell on to enable it.** A laptop
with no serial port that stops before it can draw has nowhere to say why, and
`schinitd enable` needs a prompt that does not exist.
So `make DEBUG=1` adds a sixth entry to the F8 menu. Picking it toggles;
booting carries the choice through SCHBOOT_DEBUG_FLAG; the kernel publishes
/proc/bootdebug; schinitd starts the one job that flag may un-disable, with
the manifest's Args now naming 0.0.0.0 -- a debugger reachable only from the
machine being debugged is no use when that machine is the one that will not
talk to you.
Four decisions, since this is a network-reachable debugger:
compiled out, not hidden A release build cannot set the flag because the
code that would set it is not in the binary. A
menu item that is present and refuses is an
invitation to make it not refuse.
consent at the machine Somebody standing at the keyboard during boot is
the strongest consent this system can ask for and
the weakest one that is any use on hardware that
will not boot.
one job, named in schinitd Not a manifest key like EnableIfDebug: the general
version of "the boot menu starts things that ship
off" is worse than the specific one, and this way
what the flag can start is one readable line.
the kernel only publishes Nothing in the kernel behaves differently, so a
debug kernel and a release kernel are the same
kernel and a bug cannot be the flag's fault.
/proc/bootdebug is its own file rather than a second line in /proc/bootmode,
because bootmode's whole promise is that reading it is a string compare and
not a parser.
**Also: `tone` stopped lying.** On a machine with no sound card it reported
"the device will not take 16-bit samples", which sends you looking at the
format code instead of at the empty PCI slot -- /dev/dsp exists whether or not
hardware is behind it, and the ioctl is where the absence shows up. ENODEV is
now told apart from a format refusal.
Verified end to end: F8 -> item 6 -> boot -> schinitd says it is starting
schdbgd -> a client outside the VM connected to the forwarded port and ran
`dma`, getting the real arena dump back. The release build has no such entry
in the binary at all, and boots clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
4.8 KiB
C
151 lines
4.8 KiB
C
// 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"
|
|
#include "errno.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.
|
|
//
|
|
// The two failures are told apart, because they were not and the message
|
|
// was a lie: a machine with no sound card at all reported "the device will
|
|
// not take 16-bit samples", which sends you looking at the format code
|
|
// instead of at the empty PCI slot. The node exists whether or not any
|
|
// hardware is behind it -- that is deliberate, and it is why the *ioctl*
|
|
// is where the absence shows up.
|
|
int r = ioctl(fd, SNDCTL_DSP_SETFMT, &fmt);
|
|
if (r == -ENODEV) {
|
|
say("tone: there is no audio device on this machine\n");
|
|
say(" (/dev/dsp exists on every machine; the hardware does not)\n");
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
if (r < 0 || fmt != AFMT_S16_LE) {
|
|
say("tone: the device will not take 16-bit samples (ioctl said ");
|
|
sayn(r); say(")\n");
|
|
close(fd);
|
|
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;
|
|
}
|