SchOS/kernel/arch/x86_64/ioperm.c
Nexoniarz a1c965da1e include: one folder per subsystem, and includes that name it
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
2026-10-23 15:05:12 +02:00

165 lines
5.2 KiB
C

// The TSS I/O permission bitmap. See include/arch/ioperm.h for why.
#include "core/types.h"
#include "core/defs.h"
#include "core/param.h"
#include "arch/mmu.h"
#include "core/proc.h"
#include "arch/ioperm.h"
// The 64-bit TSS, written out rather than indexed.
//
// The old code reached it as `uint *tss` and set `tss[16]` to put 0x68 in the
// I/O map base -- but the I/O map base is at offset 102, which is the high half
// of the dword at index 25, not 16. Index 16 is offset 64, in the middle of the
// interrupt stack table.
//
// That was not harmless. The field it meant to write stayed 0, and a base of 0
// with a limit of 0x67 does not mean "no bitmap" -- it means the bitmap starts
// at the top of the TSS, so the CPU checked ports 0-831 against the TSS's own
// bytes. Those bytes are RSP0: a kernel stack pointer, whose zero bits are
// *permission*. Some low ports were reachable from user mode and had been
// since the kernel was 64-bit, which nothing had any reason to notice.
//
// Naming the fields is what makes that visible, so they are named.
struct taskstate64 {
uint32 res0;
uint64 rsp0;
uint64 rsp1;
uint64 rsp2;
uint64 res1;
uint64 ist[7];
uint64 res2;
uint16 res3;
uint16 iomb; // offset from the base of the TSS
} __attribute__((packed));
#define IOMAP_BITS 65536
#define IOMAP_BYTES (IOMAP_BITS / 8)
// The TSS and its bitmap have to be one object: `iomb` is a 16-bit offset from
// the TSS base, and the TSS descriptor's limit is what cuts the bitmap off.
// So they are declared together and there is no way for them to drift apart.
//
// 8 KB per CPU of permanently resident kernel memory is the cost of being able
// to say "these ports and no others", and it is charged whether or not any
// process ever uses it. A bitmap sized to the highest port actually granted
// would be smaller and would have to be resized at runtime, under a descriptor
// the CPU is using; this is the version that is obviously correct.
struct tssblk {
struct taskstate64 ts;
uint8 map[IOMAP_BYTES];
// Intel requires a byte of all-ones past the end, so an access that straddles
// the final port still reads a deny bit rather than whatever follows.
uint8 tail;
} __attribute__((packed, aligned(16)));
static struct tssblk tssblk[NCPU];
// What each CPU currently has open. Kept per CPU rather than read back from the
// process, because switching *away* has to close what was opened and the
// process that opened it may be part-way through exit() by then.
static struct {
struct ioport_range r[NIOPORT];
int n;
} installed[NCPU];
static void
map_set(uint8 *map, uint16 base, uint16 len, int allow)
{
uint32 p, end = (uint32)base + len;
if (end > IOMAP_BITS)
end = IOMAP_BITS;
for (p = base; p < end; p++) {
if (allow)
map[p >> 3] &= ~(1 << (p & 7));
else
map[p >> 3] |= (1 << (p & 7));
}
}
void *
ioperm_tss(int cpu, uint64 *limit)
{
struct tssblk *b = &tssblk[cpu];
// Deny everything to start with. A zero bit is permission, so a zeroed
// bitmap is an open machine -- this memset is not initialisation tidiness.
memset(b->map, 0xFF, sizeof(b->map));
b->tail = 0xFF;
b->ts.iomb = (uint16)__builtin_offsetof(struct tssblk, map);
installed[cpu].n = 0;
// The limit is the offset of the last valid byte, and it has to include the
// trailing 0xFF -- stopping one byte short is the difference between a
// straddling access being denied and it reading the next object.
*limit = (uint64)__builtin_offsetof(struct tssblk, tail);
return b;
}
// The same block, without the reset. seginit() calls ioperm_tss() once per CPU
// to build it; everything after that just wants the pointer.
void *
ioperm_tss_of(int cpu)
{
return &tssblk[cpu];
}
void
ioperm_set_rsp0(void *tss, addr_t stktop)
{
((struct taskstate64 *)tss)->rsp0 = (uint64)stktop;
}
void
ioperm_grant(struct proc *p, uint16 base, uint16 len)
{
int i;
if (len == 0)
return;
for (i = 0; i < p->nioport; i++)
if (p->ioport[i].base == base && p->ioport[i].len == len)
return;
if (p->nioport >= NIOPORT)
return; // refused quietly; the driver will fault loudly
p->ioport[p->nioport].base = base;
p->ioport[p->nioport].len = len;
p->nioport++;
}
void
ioperm_revoke_all(struct proc *p)
{
p->nioport = 0;
// The bitmap is not touched here. This can run on a CPU that is not the one
// holding the process's ranges open, and the next switchuvm() on each CPU
// closes what that CPU opened -- which is the only place the two can be made
// to agree without an IPI.
}
void
ioperm_switch(struct proc *p)
{
int c = cpunum();
struct tssblk *b = &tssblk[c];
int i;
// Close whatever was open. Doing this unconditionally rather than diffing
// against the incoming process costs a few hundred bit clears on a machine
// where nobody uses ports at all -- which is `installed[c].n == 0`, and then
// this loop does nothing.
for (i = 0; i < installed[c].n; i++)
map_set(b->map, installed[c].r[i].base, installed[c].r[i].len, 0);
installed[c].n = 0;
if (!p || p->nioport == 0)
return;
for (i = 0; i < p->nioport && i < NIOPORT; i++) {
map_set(b->map, p->ioport[i].base, p->ioport[i].len, 1);
installed[c].r[i] = p->ioport[i];
}
installed[c].n = i;
}