Nothing ever saved the FPU and SSE registers on a context switch. musl's memcpy, the compiler's struct copies and any arithmetic on doubles keep live values in %xmm, so two processes sharing a CPU wrote over each other's: two shells counting in a loop on one processor took a NULL+8 page fault within seconds, at a different rip every time. It reproduces on kernels from before this week too -- it had always been there, and every crash nobody could explain was a candidate. sched() now fxsaves the process leaving the CPU and scheduler() fxrstors the one arriving. A new process starts from the ABI's state (FCW 0x37F, MXCSR 0x1F80), fork copies the parent's live registers, exec resets them, and a signal handler's are put back by rt_sigreturn so the interrupted code finds what it had. The kernel was free to use %xmm too -- GCC vectorised struct copies and memset into 153 instructions of it -- so an interrupt could clobber the registers of the program it interrupted with no switch at all. Kernel, lwIP and uACPI objects are built -mgeneral-regs-only now and the image has none; cmd/ keeps SSE. Checked under QEMU: three concurrent loops three times over, on 1 CPU and on 4, no faults; before, every run faulted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
255 lines
5.7 KiB
C
255 lines
5.7 KiB
C
#pragma once
|
|
|
|
struct dtr {
|
|
ushort limit;
|
|
uint64 base;
|
|
} __attribute__((packed));
|
|
|
|
static inline uchar
|
|
inb(ushort port)
|
|
{
|
|
uchar data;
|
|
asm volatile("in %1,%0" : "=a" (data) : "d" (port));
|
|
return data;
|
|
}
|
|
|
|
// Cycle counter. Not a clock -- it does not convert to time without knowing the
|
|
// frequency, and it is not synchronised across cores -- but for "which of these
|
|
// two paths costs more" it is exactly the right instrument, and the only one
|
|
// with fine enough resolution to see inside a single syscall.
|
|
static inline uint64
|
|
rdtsc(void)
|
|
{
|
|
uint lo, hi;
|
|
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
|
|
return ((uint64)hi << 32) | lo;
|
|
}
|
|
|
|
// The x87/MMX/SSE register file, to and from a 512-byte, 16-byte-aligned area.
|
|
static inline void
|
|
fxsave(void *area)
|
|
{
|
|
asm volatile("fxsave64 (%0)" : : "r"(area) : "memory");
|
|
}
|
|
|
|
static inline void
|
|
fxrstor(const void *area)
|
|
{
|
|
asm volatile("fxrstor64 (%0)" : : "r"(area) : "memory");
|
|
}
|
|
|
|
static inline void
|
|
insl(int port, void *addr, int cnt)
|
|
{
|
|
asm volatile("cld; rep insl" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"d" (port), "0" (addr), "1" (cnt) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
static inline void
|
|
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)
|
|
{
|
|
uint data;
|
|
asm volatile("inl %1,%0" : "=a" (data) : "d" (port));
|
|
return data;
|
|
}
|
|
|
|
static inline void
|
|
outw(ushort port, ushort data)
|
|
{
|
|
asm volatile("out %0,%1" : : "a" (data), "d" (port));
|
|
}
|
|
|
|
// PCI configuration space is reached through a pair of 32-bit ports, so this
|
|
// is not optional decoration -- see kernel/dev/pci.c.
|
|
static inline void
|
|
outl(ushort port, uint data)
|
|
{
|
|
asm volatile("outl %0,%1" : : "a" (data), "d" (port));
|
|
}
|
|
|
|
static inline void
|
|
outsl(int port, const void *addr, int cnt)
|
|
{
|
|
asm volatile("cld; rep outsl" :
|
|
"=S" (addr), "=c" (cnt) :
|
|
"d" (port), "0" (addr), "1" (cnt) :
|
|
"cc");
|
|
}
|
|
|
|
static inline void
|
|
stosb(void *addr, int data, int cnt)
|
|
{
|
|
asm volatile("cld; rep stosb" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"0" (addr), "1" (cnt), "a" (data) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
// 64-bit-only helpers. `rep stosq` and `xchgq` do not exist in 32-bit mode, so
|
|
// these must not even be parsed by the -m32 bootloader build -- they compiled
|
|
// there before only because uint64 was accidentally 32 bits wide, which is the
|
|
// bug include/types.h now asserts against.
|
|
#ifdef __x86_64__
|
|
static inline void
|
|
stosq(void *addr, uint64 data, int cnt)
|
|
{
|
|
asm volatile("cld; rep stosq" :
|
|
"=D" (addr), "=c" (cnt) :
|
|
"0" (addr), "1" (cnt), "a" (data) :
|
|
"memory", "cc");
|
|
}
|
|
|
|
struct segdesc;
|
|
|
|
static inline void
|
|
lgdt(struct segdesc *p, int size)
|
|
{
|
|
struct dtr d;
|
|
d.limit = size - 1;
|
|
d.base = (uint64)p;
|
|
asm volatile("lgdt (%0)" : : "r" (&d));
|
|
}
|
|
|
|
struct gatedesc;
|
|
|
|
static inline void
|
|
lidt(struct gatedesc *p, int size)
|
|
{
|
|
struct dtr d;
|
|
d.limit = size - 1;
|
|
d.base = (uint64)p;
|
|
asm volatile("lidt (%0)" : : "r" (&d));
|
|
}
|
|
|
|
static inline void
|
|
ltr(ushort sel)
|
|
{
|
|
asm volatile("ltr %0" : : "r" (sel));
|
|
}
|
|
|
|
static inline uint64
|
|
readeflags(void)
|
|
{
|
|
uint64 flags;
|
|
asm volatile("pushfq; pop %0" : "=r" (flags));
|
|
return flags;
|
|
}
|
|
|
|
static inline void
|
|
cli(void)
|
|
{
|
|
asm volatile("cli");
|
|
}
|
|
|
|
static inline void
|
|
sti(void)
|
|
{
|
|
asm volatile("sti");
|
|
}
|
|
|
|
static inline void
|
|
hlt(void)
|
|
{
|
|
asm volatile("hlt");
|
|
}
|
|
|
|
static inline uint64
|
|
xchg(volatile uint64 *addr, uint64 newval)
|
|
{
|
|
uint64 result;
|
|
asm volatile("lock; xchgq %0, %1" :
|
|
"+m" (*addr), "=a" (result) :
|
|
"1" (newval) :
|
|
"cc");
|
|
return result;
|
|
}
|
|
#endif /* __x86_64__ */
|
|
|
|
static inline uint64
|
|
rcr2(void)
|
|
{
|
|
uint64 val;
|
|
asm volatile("mov %%cr2,%0" : "=r" (val));
|
|
return val;
|
|
}
|
|
|
|
static inline void
|
|
lcr3(uint64 val)
|
|
{
|
|
asm volatile("mov %0,%%cr3" : : "r" (val));
|
|
}
|
|
|
|
// Invalidate the TLB entry for one page. Needed any time a PTE is changed
|
|
// or cleared for an address space that may still be the active CR3 —
|
|
// otherwise the CPU can keep translating through the stale entry until the
|
|
// next full CR3 reload (context switch), silently reading/writing the
|
|
// wrong physical page.
|
|
static inline void
|
|
invlpg(void *va)
|
|
{
|
|
asm volatile("invlpg (%0)" : : "r" (va) : "memory");
|
|
}
|
|
|
|
static inline void
|
|
wrmsr(uint32 msr, uint64 val)
|
|
{
|
|
uint32 low = (uint32)val;
|
|
uint32 high = (uint32)(val >> 32);
|
|
asm volatile("wrmsr" : : "c" (msr), "a" (low), "d" (high));
|
|
}
|
|
|
|
static inline uint64
|
|
rdmsr(uint32 msr)
|
|
{
|
|
uint32 low, high;
|
|
asm volatile("rdmsr" : "=a" (low), "=d" (high) : "c" (msr));
|
|
return ((uint64)high << 32) | low;
|
|
}
|
|
|
|
static inline uint64
|
|
readcs(void)
|
|
{
|
|
uint64 val;
|
|
asm volatile("mov %%cs, %0" : "=r" (val));
|
|
return val;
|
|
}
|
|
|
|
// CPUID. `leaf` in EAX, results in EAX/EBX/ECX/EDX.
|
|
//
|
|
// %rbx is callee-saved *and* the PIC base register on some builds, so it is
|
|
// exchanged out rather than named as an operand -- naming it directly is the
|
|
// classic way to get "inline asm clobbers reserved register" or, worse, silent
|
|
// corruption in the caller.
|
|
static inline void
|
|
cpuid_leaf(uint32 leaf, uint32 *a, uint32 *b, uint32 *c, uint32 *d)
|
|
{
|
|
uint32 ra, rb, rc, rd;
|
|
asm volatile("xchgq %%rbx, %q1\n\t"
|
|
"cpuid\n\t"
|
|
"xchgq %%rbx, %q1"
|
|
: "=a"(ra), "=&r"(rb), "=c"(rc), "=d"(rd)
|
|
: "0"(leaf), "2"(0));
|
|
if (a) *a = ra;
|
|
if (b) *b = rb;
|
|
if (c) *c = rc;
|
|
if (d) *d = rd;
|
|
}
|