kernel: the clock ran 7x slow because the GPU never acknowledged its interrupt
fastfetch reported 3 seconds of uptime after 15 real ones, `time` claimed
3.12s for a 20-second command, and ping's one-second interval was closer to
six. One cause, not three: `ticks` was advancing at ~14.5 Hz instead of 100.
It was not the timer. virtio-gpu-pci shares IRQ 11 with the e1000 and
registered no interrupt handler at all. PCI INTx is level-triggered, so the
device held the line asserted, the kernel signalled EOI, and it fired again
immediately -- 72,000 interrupts a second, 83% of CPU0, starving the tick.
The e1000's handler read ICR as zero half a million times, correctly
answering "not mine", which is what made it look like a network-free problem.
Reading virtio's ISR byte clears it and drops the line. That is the fix.
irq_dispatch now measures each line's rate against the TSC -- not ticks,
which is the first casualty of a storm -- and names the attached handlers
once above 20000/s. It reproduces the original fault as a single line.
Found along the way, and fixed because they were real:
- lapicinit loaded TICR with xv6's uncalibrated 10000000, whose own comment
admitted it was never measured. calibrate_timers() now measures the LAPIC
and the TSC against the 8254 PIT. (It came back at 999.9 MHz, so this was
not the bug -- but the next machine is not obliged to be 1 GHz.)
- microdelay() was an empty function body. Every microdelay(1000) in the
xHCI reset path and the USB hub power-on sequence returned instantly.
- HZ is a constant in param.h now, not the literal 100 written out at a
dozen ticks-to-seconds conversions. USER_HZ is separate and deliberately
does not follow it: musl answers sysconf(_SC_CLK_TCK) with 100 always, so
times() and /proc/stat scale at the syscall boundary the way Linux does.
- TIOCGWINSZ answered a hardcoded 24x80. gfx_cols()/gfx_rows() have known
the real geometry all along; at 1024x768 the console is 128x48, so every
program that lays out a screen was formatting for the wrong one.
- TCFLSH fell through to the catch-all `return 0`, claiming to discard
input without discarding any.
uptime now advances 5.01 seconds per 5 real seconds. schfs-check passes.
Full investigation, including the two wrong hypotheses and the measurement
flaw that protected them, in docs/history/kernel.md section 13.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
c874811679
commit
9d4cdb44fc
17 changed files with 479 additions and 37 deletions
|
|
@ -49,6 +49,85 @@ lapicw(int index, int value)
|
|||
lapic[ID]; // wait for write to finish, by reading
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Timer calibration
|
||||
// ---------------------------------------------------------------------
|
||||
// The LAPIC timer counts down at the APIC bus frequency, and nothing in the
|
||||
// architecture tells you what that frequency is. xv6 loaded TICR with
|
||||
// 10000000 and hoped; its own comment admitted the gap -- "if xv6 cared more
|
||||
// about precise timekeeping, TICR would be calibrated using an external time
|
||||
// source." That value yields the intended 100 Hz only if the bus happens to
|
||||
// run at exactly 1 GHz.
|
||||
//
|
||||
// Under KVM it does not, and the error is not subtle. The kernel believed
|
||||
// ticks arrived 100 times a second while they were arriving roughly 18 times
|
||||
// a second, so every number derived from ticks was wrong by that same factor,
|
||||
// in the same direction, everywhere at once: uptime ran slow, `time` reported
|
||||
// three seconds for twenty, ping's one-second interval stretched to six. It
|
||||
// read as three unrelated bugs in three unrelated programs. It was this line.
|
||||
//
|
||||
// So measure it. The 8254 PIT is exactly the external time source the comment
|
||||
// asked for: its input clock is fixed at 1193182 Hz by the hardware, it is
|
||||
// always present, and channel 2 is the one whose gate software can raise and
|
||||
// whose output software can poll -- a stopwatch that needs no interrupt, which
|
||||
// matters because this runs before the IDT is ready to take one.
|
||||
#define PIT_HZ 1193182 // 8254 input clock, fixed by definition
|
||||
#define PIT_CH2 0x42
|
||||
#define PIT_CMD 0x43
|
||||
#define PIT_GATE 0x61 // bit 0: ch2 gate, bit 1: speaker, bit 5: ch2 output
|
||||
|
||||
uint lapic_timer_hz; // measured LAPIC timer counts per second
|
||||
uint64 tsc_hz; // measured TSC cycles per second
|
||||
|
||||
static void
|
||||
calibrate_timers(void)
|
||||
{
|
||||
// 50 ms: long enough that the port I/O around the measurement is noise,
|
||||
// short enough that nobody notices it in boot time. The count fits in the
|
||||
// 16 bits the PIT has (59659), which is the real upper bound here.
|
||||
const uint us = 50000;
|
||||
uint count = (uint)(((uint64)PIT_HZ * us) / 1000000);
|
||||
|
||||
// Channel 2, mode 0 (interrupt on terminal count), lobyte then hibyte.
|
||||
// Gate high so it counts; the speaker bit stays clear so the box is silent.
|
||||
outb(PIT_GATE, (inb(PIT_GATE) & ~0x02) | 0x01);
|
||||
outb(PIT_CMD, 0xB0);
|
||||
outb(PIT_CH2, count & 0xFF);
|
||||
outb(PIT_CH2, (count >> 8) & 0xFF);
|
||||
|
||||
// A free-running LAPIC counter for the duration: one-shot, masked, started
|
||||
// at the largest count there is so it cannot wrap inside 50 ms.
|
||||
lapicw(TDCR, X1);
|
||||
lapicw(TIMER, MASKED);
|
||||
lapicw(TICR, 0xFFFFFFFF);
|
||||
|
||||
uint64 tsc0 = rdtsc();
|
||||
uint lap0 = lapic[TCCR];
|
||||
|
||||
while (!(inb(PIT_GATE) & 0x20)) // ch2 output rises at terminal count
|
||||
;
|
||||
|
||||
uint lap1 = lapic[TCCR];
|
||||
uint64 tsc1 = rdtsc();
|
||||
|
||||
lapicw(TICR, 0); // stop counting
|
||||
|
||||
// TCCR counts down, so elapsed counts are start minus end.
|
||||
lapic_timer_hz = (uint)(((uint64)(lap0 - lap1) * 1000000) / us);
|
||||
tsc_hz = ((tsc1 - tsc0) * 1000000) / us;
|
||||
|
||||
// If the measurement came back implausible -- no LAPIC, a hypervisor that
|
||||
// stops the counter, a PIT that never raises its output -- fall back to the
|
||||
// old assumption rather than dividing by zero and taking the machine down.
|
||||
// Wrong-but-running beats a triple fault, and the printed value says which
|
||||
// case this is.
|
||||
if (lapic_timer_hz < HZ * 1000u) {
|
||||
cprintf("cpu0: lapic calibration implausible (%u Hz), assuming 1 GHz bus\n",
|
||||
lapic_timer_hz);
|
||||
lapic_timer_hz = 1000000000u; // the 1 GHz that TICR=10000000 implied
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
lapicinit(void)
|
||||
{
|
||||
|
|
@ -58,13 +137,19 @@ lapicinit(void)
|
|||
// Enable local APIC; set spurious interrupt vector.
|
||||
lapicw(SVR, ENABLE | (T_IRQ0 + IRQ_SPURIOUS));
|
||||
|
||||
// The timer repeatedly counts down at bus frequency
|
||||
// from lapic[TICR] and then issues an interrupt.
|
||||
// If xv6 cared more about precise timekeeping,
|
||||
// TICR would be calibrated using an external time source.
|
||||
// The timer counts down at the APIC bus frequency and interrupts at zero.
|
||||
// What that frequency *is*, calibrate_timers() had to go and measure --
|
||||
// see the comment above it for why loading a constant here does not work.
|
||||
if (lapic_timer_hz == 0) {
|
||||
calibrate_timers();
|
||||
cprintf("cpu0: lapic timer %u Hz, tsc %u MHz, tick = %u counts at %d Hz\n",
|
||||
lapic_timer_hz, (uint)(tsc_hz / 1000000),
|
||||
lapic_timer_hz / HZ, HZ);
|
||||
}
|
||||
|
||||
lapicw(TDCR, X1);
|
||||
lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));
|
||||
lapicw(TICR, 10000000);
|
||||
lapicw(TICR, lapic_timer_hz / HZ);
|
||||
|
||||
// Disable logical interrupt lines.
|
||||
lapicw(LINT0, MASKED);
|
||||
|
|
@ -132,10 +217,36 @@ lapiceoi(void)
|
|||
}
|
||||
|
||||
// Spin for a given number of microseconds.
|
||||
// On real hardware would want to tune this dynamically.
|
||||
//
|
||||
// This was an empty function body -- xv6's stub, carrying a comment saying
|
||||
// real hardware would want to tune it dynamically. Nothing ever did, so every
|
||||
// microdelay() in the tree returned immediately: the xHCI reset sequence, the
|
||||
// USB hub power-on wait, the UART drain. Those delays are written into the
|
||||
// specifications because the hardware genuinely needs the time, and a port
|
||||
// that "works anyway" is one that is racing and winning.
|
||||
//
|
||||
// The TSC is the right clock for this: calibrate_timers() measured it in the
|
||||
// same pass as the LAPIC, and unlike the LAPIC timer it is not already busy
|
||||
// driving the tick.
|
||||
void
|
||||
microdelay(int us)
|
||||
{
|
||||
if (us <= 0)
|
||||
return;
|
||||
|
||||
// Before calibration (early boot, and the AP trampoline) there is no
|
||||
// measured frequency to work from. Spin on a rough count instead: the
|
||||
// figure is wrong, but being approximately late is the entire point of the
|
||||
// call, and being instantly early is what broke it.
|
||||
if (tsc_hz == 0) {
|
||||
for (volatile int i = 0; i < us * 100; i++)
|
||||
;
|
||||
return;
|
||||
}
|
||||
|
||||
uint64 end = rdtsc() + ((uint64)us * tsc_hz) / 1000000;
|
||||
while (rdtsc() < end)
|
||||
asm volatile("pause");
|
||||
}
|
||||
|
||||
#define CMOS_PORT 0x70
|
||||
|
|
|
|||
|
|
@ -567,6 +567,23 @@ cons_set_termios(const struct termios *in)
|
|||
wakeup(&input.r);
|
||||
}
|
||||
|
||||
// Discard everything typed but not yet consumed: completed lines waiting in
|
||||
// the buffer and the partial line still being edited. TCFLSH (tcflush) is the
|
||||
// caller, and a line editor issues one whenever it takes the terminal back --
|
||||
// after an interrupt, or when a command that read from the tty exits. Without
|
||||
// it the keys pressed while the last command ran arrive at the next prompt.
|
||||
//
|
||||
// A pending EOF goes too: end of input is buffer state like any other, and
|
||||
// leaving it set would end the *next* read instead of the one it was typed at.
|
||||
void
|
||||
cons_flush_input(void)
|
||||
{
|
||||
acquire(&input.lock);
|
||||
input.e = input.w = input.r;
|
||||
input.eof = 0;
|
||||
release(&input.lock);
|
||||
}
|
||||
|
||||
static void
|
||||
echo(int c)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ gather(char *reason, addr_t *pcs, int npcs)
|
|||
{
|
||||
char *p = put_dec(f->value, e, (uint64)ticks);
|
||||
p = put_str(p, e, " ticks (about ");
|
||||
p = put_dec(p, e, (uint64)ticks / 100);
|
||||
p = put_dec(p, e, (uint64)ticks / HZ);
|
||||
put_str(p, e, " s)");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ time_init(void)
|
|||
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
|
||||
// the moment this function ran. It is only a fraction of a second, but it
|
||||
// keeps boot_epoch meaning exactly what its name says.
|
||||
uint64 up = ticks / 100;
|
||||
uint64 up = ticks / HZ;
|
||||
boot_epoch = now > up ? now - up : now;
|
||||
|
||||
cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n",
|
||||
|
|
@ -110,7 +110,7 @@ wallclock(uint64 *sec, uint64 *usec)
|
|||
release(&tickslock);
|
||||
|
||||
if (sec)
|
||||
*sec = boot_epoch + xticks / 100;
|
||||
*sec = boot_epoch + xticks / HZ;
|
||||
if (usec)
|
||||
*usec = (uint64)(xticks % 100) * 10000;
|
||||
*usec = (uint64)(xticks % HZ) * (1000000 / HZ);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ input_report(int dev, int type, int code, int value)
|
|||
d->r++;
|
||||
|
||||
e = &d->ev[d->w % NEVENT];
|
||||
e->time.tv_sec = ticks / 100;
|
||||
e->time.tv_usec = (ticks % 100) * 10000;
|
||||
e->time.tv_sec = ticks / HZ;
|
||||
e->time.tv_usec = (ticks % HZ) * USEC_PER_TICK;
|
||||
e->type = (uint16)type;
|
||||
e->code = (uint16)code;
|
||||
e->value = (int32)value;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "traps.h"
|
||||
#include "spinlock.h"
|
||||
#include "irq.h"
|
||||
#include "x86.h"
|
||||
|
||||
struct irq_action {
|
||||
irq_handler_t fn;
|
||||
|
|
@ -134,6 +135,62 @@ irq_unregister(int irq, irq_handler_t fn, void *arg)
|
|||
release(&irqlock);
|
||||
}
|
||||
|
||||
// Notice a line that is firing far faster than any real device would, and say
|
||||
// so once.
|
||||
//
|
||||
// A level-triggered PCI interrupt that nobody acknowledges re-fires the instant
|
||||
// EOI is signalled, so the line runs at whatever rate the CPU can take traps --
|
||||
// tens of thousands per second. That starves everything else on the machine,
|
||||
// and it does so silently: the symptom appears wherever the missing CPU time
|
||||
// hurts first, which is nowhere near the device at fault.
|
||||
//
|
||||
// It cost a long afternoon to find one of these. virtio-gpu shared IRQ 11 with
|
||||
// the e1000 and registered no handler, so the line storm at ~72 kHz and took
|
||||
// 83% of CPU0. What that looked like from userspace was a clock running seven
|
||||
// times slow -- uptime, `time`, and ping's interval all wrong together, which
|
||||
// reads as a timekeeping bug and sends you into the timer code. The timer was
|
||||
// fine the whole time. This is the message that would have said so.
|
||||
//
|
||||
// The rate is measured against the TSC, deliberately: `ticks` is the first
|
||||
// casualty of a storm, so a storm detector that counted ticks would go quiet
|
||||
// exactly when it was needed.
|
||||
#define STORM_SAMPLE 4096 // dispatches between rate checks
|
||||
#define STORM_HZ 20000 // per second; no real device sustains this
|
||||
|
||||
static void
|
||||
storm_check(int irq)
|
||||
{
|
||||
static uint64 sample_tsc[NIRQ];
|
||||
static uint sample_n[NIRQ];
|
||||
static uchar reported[NIRQ];
|
||||
|
||||
if(reported[irq] || !tsc_hz)
|
||||
return;
|
||||
if(++sample_n[irq] < STORM_SAMPLE)
|
||||
return;
|
||||
|
||||
uint64 now = rdtsc(), prev = sample_tsc[irq];
|
||||
sample_n[irq] = 0;
|
||||
sample_tsc[irq] = now;
|
||||
if(!prev)
|
||||
return; // first sample: no interval to divide by
|
||||
|
||||
uint64 elapsed = now - prev;
|
||||
if(elapsed == 0)
|
||||
return;
|
||||
uint64 rate = (uint64)STORM_SAMPLE * tsc_hz / elapsed;
|
||||
if(rate < STORM_HZ)
|
||||
return;
|
||||
|
||||
reported[irq] = 1;
|
||||
cprintf("irq %d: %d interrupts/second -- a device on this line is not "
|
||||
"acknowledging it.\n", irq, (int)rate);
|
||||
for(int i = 0; i < line[irq].n; i++)
|
||||
cprintf("irq %d: handler '%s' is attached\n", irq, line[irq].act[i].name);
|
||||
if(line[irq].n == 0)
|
||||
cprintf("irq %d: no handler is attached at all\n", irq);
|
||||
}
|
||||
|
||||
void
|
||||
irq_dispatch(int irq)
|
||||
{
|
||||
|
|
@ -145,6 +202,7 @@ irq_dispatch(int irq)
|
|||
n = line[irq].n;
|
||||
if(n == 0){
|
||||
line[irq].unhandled++;
|
||||
storm_check(irq);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +212,7 @@ irq_dispatch(int irq)
|
|||
line[irq].act[i].count++;
|
||||
line[irq].act[i].fn(line[irq].act[i].arg);
|
||||
}
|
||||
storm_check(irq);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -130,11 +130,12 @@ gen_meminfo(struct sbuf *sb)
|
|||
static void
|
||||
gen_uptime(struct sbuf *sb)
|
||||
{
|
||||
uint t = uptime(); // timer ticks, 100 Hz
|
||||
sb_putu(sb, t / 100);
|
||||
uint t = uptime(); // timer ticks, HZ of them per second
|
||||
uint frac = (t % HZ) * 100 / HZ; // hundredths, whatever HZ happens to be
|
||||
sb_putu(sb, t / HZ);
|
||||
sb_putc(sb, '.');
|
||||
sb_putu(sb, (t % 100) / 10);
|
||||
sb_putu(sb, (t % 100) % 10);
|
||||
sb_putu(sb, frac / 10);
|
||||
sb_putu(sb, frac % 10);
|
||||
sb_puts(sb, " 0.00\n"); // idle time: not tracked
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +215,8 @@ gen_version(struct sbuf *sb)
|
|||
static void
|
||||
gen_stat(struct sbuf *sb)
|
||||
{
|
||||
uint t = uptime();
|
||||
// USER_HZ, like times() -- everything that parses /proc/stat divides by 100.
|
||||
uint t = (uint)((uint64)uptime() * USER_HZ / HZ);
|
||||
sb_puts(sb, "cpu ");
|
||||
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
|
||||
// Online, for the same reason as cpuinfo: top and friends count the cpuN
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "irq.h"
|
||||
#include "param.h"
|
||||
#include "x86.h"
|
||||
#include "spinlock.h"
|
||||
|
|
@ -35,6 +36,9 @@
|
|||
struct vgpu {
|
||||
volatile uint8 *common; // common configuration structure
|
||||
volatile uint8 *notify; // notification area
|
||||
// The interrupt status byte. Reading it is what tells the device we have
|
||||
// noticed, and therefore what makes it drop the PCI interrupt line.
|
||||
volatile uint8 *isr;
|
||||
uint32 notify_mult; // queue_notify_off multiplier
|
||||
|
||||
// The queue. Allocated as one contiguous block from the DMA arena, because
|
||||
|
|
@ -396,6 +400,33 @@ const struct display_ops virtio_gpu_display_ops = {
|
|||
|
||||
// --- probe -----------------------------------------------------------------
|
||||
|
||||
// Acknowledge the device's interrupt.
|
||||
//
|
||||
// This driver polls for command completion and wants no notifications at all,
|
||||
// which is why nothing here reacts to the cause -- but "wants none" and "gets
|
||||
// none" are different things, and a PCI interrupt line is level-triggered.
|
||||
// The device raises INTx, the CPU takes the interrupt, the kernel signals EOI,
|
||||
// and if nobody has cleared the device's interrupt status the line is still
|
||||
// asserted, so it fires again immediately. Forever.
|
||||
//
|
||||
// That is not hypothetical. With no handler registered here at all, this
|
||||
// device shared IRQ 11 with the e1000 under QEMU and storme at roughly 72,000
|
||||
// interrupts a second, eating 83% of CPU0. The visible symptom was nowhere
|
||||
// near the graphics driver: the timer interrupt was starved, so `ticks`
|
||||
// advanced at about 14 Hz instead of 100, and every duration in the system --
|
||||
// uptime, `time`, sleep, ping's one-second interval -- came out ~7x short.
|
||||
// See docs/history/kernel.md.
|
||||
//
|
||||
// Reading the ISR byte clears it and deasserts the line. That single read is
|
||||
// the entire handler, and it is not optional.
|
||||
static void
|
||||
vgpu_intr(void *arg)
|
||||
{
|
||||
struct vgpu *g = arg;
|
||||
if (g && g->isr)
|
||||
(void)*g->isr;
|
||||
}
|
||||
|
||||
static int
|
||||
vgpu_probe(struct pci_dev *d)
|
||||
{
|
||||
|
|
@ -414,6 +445,7 @@ vgpu_probe(struct pci_dev *d)
|
|||
|
||||
g->common = find_cfg(d, VIRTIO_PCI_CAP_COMMON_CFG, 0);
|
||||
g->notify = find_cfg(d, VIRTIO_PCI_CAP_NOTIFY_CFG, &g->notify_mult);
|
||||
g->isr = find_cfg(d, VIRTIO_PCI_CAP_ISR_CFG, 0);
|
||||
if (!g->common || !g->notify) {
|
||||
cprintf("virtio-gpu: no modern virtio capabilities; not a 1.0 device\n");
|
||||
return -1;
|
||||
|
|
@ -465,9 +497,16 @@ vgpu_probe(struct pci_dev *d)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Level-triggered and active low, like every other PCI INTx line -- see
|
||||
// vgpu_intr for what happens when this registration is missing.
|
||||
if (g->isr)
|
||||
irq_register(d->irq, vgpu_intr, g, "virtio-gpu", 1);
|
||||
else
|
||||
cprintf("virtio-gpu: no ISR capability; leaving irq %d unclaimed\n", d->irq);
|
||||
|
||||
g->ready = 1;
|
||||
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d\n",
|
||||
g->width, g->height, g->fb_bytes / 1024, g->qsize);
|
||||
cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d, irq %d\n",
|
||||
g->width, g->height, g->fb_bytes / 1024, g->qsize, d->irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
#include "schfs.h" /* SCHFS_BLKSIZE, SCHFS_NAME_MAX — statfs reports them */
|
||||
#include "gfx.h" /* gfx_cols/gfx_rows — TIOCGWINSZ reports them */
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Linux-ABI-compatible struct stat (x86_64 "struct stat" from
|
||||
|
|
@ -1042,10 +1043,41 @@ addr_t sys_ioctl(void)
|
|||
if (request == TIOCGWINSZ) {
|
||||
struct winsize *ws = (struct winsize*)user_ptr(argp, sizeof(*ws));
|
||||
if (!ws) return -EFAULT;
|
||||
ws->ws_row = 24; ws->ws_col = 80; ws->ws_xpixel = ws->ws_ypixel = 0;
|
||||
// The console's real geometry, not a VT100's.
|
||||
//
|
||||
// This used to answer 24x80 unconditionally -- the size of a terminal
|
||||
// SchOS has never drawn. Every program that lays out a screen asks
|
||||
// this one question first: a line editor to decide where to wrap, `ls`
|
||||
// to pick a column count, fastfetch to place its logo beside the text.
|
||||
// At 1024x768 with the 8x16 font the console is 128x48, so all of them
|
||||
// were formatting for a screen a third the area of the real one, and
|
||||
// each looked like a separate bug in a separate program.
|
||||
int cols = gfx_cols(), rows = gfx_rows();
|
||||
// Serial-only boot, with no framebuffer attached: 80x24 is then a
|
||||
// stated default rather than a wrong measurement.
|
||||
ws->ws_col = cols > 0 ? (uint16)cols : 80;
|
||||
ws->ws_row = rows > 0 ? (uint16)rows : 24;
|
||||
ws->ws_xpixel = (uint16)gfx_width();
|
||||
ws->ws_ypixel = (uint16)gfx_height();
|
||||
return 0;
|
||||
}
|
||||
// The console's size is a property of the framebuffer, so a request to
|
||||
// change it cannot be honoured -- but it is not an error either: programs
|
||||
// set it to propagate a size they already learned. Accept and ignore.
|
||||
if (request == TIOCSWINSZ) return 0;
|
||||
// TCFLSH discards buffered input, output, or both. Unlike every other
|
||||
// request here the argument is the queue selector *by value*, not a
|
||||
// pointer -- passing it to user_ptr would fault on a legitimate call.
|
||||
//
|
||||
// Output needs no action: the console writes straight to the framebuffer
|
||||
// and has no queue to drop. Input does, and answering 0 without draining
|
||||
// it -- which is what the catch-all below did -- is how a keystroke typed
|
||||
// during a command survives the flush and lands in the next prompt.
|
||||
if (request == TCFLSH) {
|
||||
if (argp == TCIFLUSH || argp == TCIOFLUSH)
|
||||
cons_flush_input();
|
||||
return 0;
|
||||
}
|
||||
// Controlling-terminal foreground process group.
|
||||
//
|
||||
// TIOCGPGRP used to answer with myproc()->pid, which happened to agree with
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ sys_poll(void)
|
|||
if (nfds > 0 && copy_from_user(fds, ufds, nfds * sizeof(struct pollfd)) < 0)
|
||||
return -EFAULT;
|
||||
|
||||
int timeout_ticks = timeout_ms < 0 ? -1 : (timeout_ms + 9) / 10;
|
||||
// Rounded up: a caller asking for any wait at all must not get zero ticks,
|
||||
// which poll() below treats as "return immediately".
|
||||
int timeout_ticks = timeout_ms < 0 ? -1
|
||||
: (timeout_ms + MSEC_PER_TICK - 1) / MSEC_PER_TICK;
|
||||
int r = do_poll(fds, nfds, timeout_ticks);
|
||||
|
||||
if (r >= 0 && nfds > 0 &&
|
||||
|
|
@ -123,7 +126,7 @@ sys_ppoll(void)
|
|||
int64 ts[2]; // {tv_sec, tv_nsec}
|
||||
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
|
||||
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
|
||||
}
|
||||
|
||||
int r = do_poll(fds, nfds, timeout_ticks);
|
||||
|
|
@ -178,7 +181,7 @@ sys_select(void)
|
|||
int64 tv[2]; // {tv_sec, tv_usec}
|
||||
if (copy_from_user(tv, utv, sizeof(tv)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(tv[0] * 100 + tv[1] / 10000);
|
||||
timeout_ticks = (int)(tv[0] * HZ + tv[1] / USEC_PER_TICK);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
|
|
@ -252,7 +255,7 @@ sys_pselect6(void)
|
|||
int64 ts[2];
|
||||
if (copy_from_user(ts, utsp, sizeof(ts)) < 0)
|
||||
return -EFAULT;
|
||||
timeout_ticks = (int)(ts[0] * 100 + ts[1] / 10000000);
|
||||
timeout_ticks = (int)(ts[0] * HZ + ts[1] / NSEC_PER_TICK);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
|
|
|
|||
|
|
@ -395,8 +395,8 @@ addr_t sys_getrusage(void)
|
|||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
ru->ru_utime.tv_sec = (long)(xt / 100);
|
||||
ru->ru_utime.tv_usec = (long)((xt % 100) * 10000);
|
||||
ru->ru_utime.tv_sec = (long)(xt / HZ);
|
||||
ru->ru_utime.tv_usec = (long)((xt % HZ) * (1000000 / HZ));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +411,11 @@ addr_t sys_times(void)
|
|||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
return (addr_t)xt; // return clock ticks since boot
|
||||
// Scaled to USER_HZ, not returned raw. musl answers sysconf(_SC_CLK_TCK)
|
||||
// with 100 unconditionally, so a shell's `time` builtin divides whatever
|
||||
// this returns by 100 -- handing it the internal tick count is only right
|
||||
// while HZ happens to equal 100, and silently wrong the day it does not.
|
||||
return (addr_t)((uint64)xt * USER_HZ / HZ);
|
||||
}
|
||||
|
||||
addr_t sys_sysinfo(void)
|
||||
|
|
@ -424,7 +428,7 @@ addr_t sys_sysinfo(void)
|
|||
acquire(&tickslock);
|
||||
uint xt = ticks;
|
||||
release(&tickslock);
|
||||
si->uptime = (long)(xt / 100);
|
||||
si->uptime = (long)(xt / HZ);
|
||||
// Real values: PHYSTOP is the top of usable physical memory and the page
|
||||
// allocator knows exactly how much of it is still on the free list. These
|
||||
// used to be the constants 256MB/128MB, invented and always wrong.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ sys_alarm(void)
|
|||
int seconds;
|
||||
if (argint(0, &seconds) < 0)
|
||||
return -1;
|
||||
myproc()->alarm_ticks = seconds * 100;
|
||||
myproc()->alarm_ticks = seconds * HZ;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ sys_nanosleep(void)
|
|||
if (sec > 0) {
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks - t0 < (uint)(sec * 100)) {
|
||||
while (ticks - t0 < (uint)(sec * HZ)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
||||
sleep(&ticks, &tickslock);
|
||||
}
|
||||
|
|
@ -89,8 +89,8 @@ sys_setitimer(void)
|
|||
if (which == 0 && newp) {
|
||||
struct itimerval iv;
|
||||
if (copy_from_user(&iv, newp, sizeof(iv)) == 0)
|
||||
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * 100 +
|
||||
iv.it_value.tv_usec / 10000);
|
||||
myproc()->alarm_ticks = (int)(iv.it_value.tv_sec * HZ +
|
||||
iv.it_value.tv_usec / USEC_PER_TICK);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -129,7 +129,7 @@ sys_clock_nanosleep(void)
|
|||
if (sec > 0) {
|
||||
acquire(&tickslock);
|
||||
uint t0 = ticks;
|
||||
while (ticks - t0 < (uint)(sec * 100)) {
|
||||
while (ticks - t0 < (uint)(sec * HZ)) {
|
||||
if (myproc()->killed) { release(&tickslock); return -EINTR; }
|
||||
sleep(&ticks, &tickslock);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue