SchOS/ldls/kpi/io.c
Nexoniarz 26bca020b6 licence: SchOS is GPL-2.0-or-later, no longer Apache-2.0
Nexoniarz is the sole author and SchOS was never published under
Apache-2.0, so this is a straight relicence. LICENSE is the FSF GPLv2 text,
every first-party SPDX header moves to GPL-2.0-or-later, and NOTICE,
README.md, docs/licensing.md and the site say so. projects/schfs-linux
follows the rest of the tree.

The GPL gives copyleft: a fork has to ship its source. It also lifts the
old "Linux drivers, never" rule, since GPL-2.0-only code combines with
GPL-2.0-or-later. "Or later" leaves room for GPLv3, but one binary still
has to pick one version, so docs/licensing.md now keeps Apache-2.0 and
GPLv3 code out of the kernel to keep Linux code possible there. BSD
4-clause code goes from "avoid" to "no", because the advertising clause
is GPL-incompatible.

LDLS still ships no .ko. That boundary is now project policy rather than
a legal requirement, and ldls.md, devices.md and licensing.md section 6
say so. refmod declares license=GPL, and ldls-check passes (27 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:48:45 +02:00

96 lines
2.7 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// Register access. See ldls/kpi/kpi.h for the provenance rule.
//
// These are the busiest symbols by a wide margin -- 538 of 8139too's 552 probe
// calls are two of them -- and they are also the shortest, because once a BAR
// is mapped a register access *is* a memory access. The whole of what these
// add over a plain dereference is the `volatile`, which stops a compiler
// deciding that reading the same address twice is redundant. For a status
// register, reading it twice is the entire point.
//
// The argument order is worth noting because it is the opposite of what a C
// programmer expects and getting it backwards produces something that almost
// works: Linux's writes take the **value first** and the address second.
#include "types.h"
#include "kpi.h"
// Every access is checked against the mapping `pci_iomap` returned.
//
// A driver that reaches outside its own BAR would otherwise take the process
// down with a page fault, and the report would be an address with no context.
// This turns that into a line naming the offset it wanted -- and since there
// is no IOMMU, a hosted driver's stray accesses are exactly the class of thing
// that has to be noticed early or not at all.
int
kpi_io_ok(void *addr, int width, int write)
{
unsigned long a = (unsigned long)addr;
if (kpi.io_base && a >= kpi.io_base &&
a + (unsigned long)width <= kpi.io_base + kpi.io_len)
return 1;
kpi.io_stray++;
if (kpi.io_stray <= 8) {
kpi_log(" kpi: the driver tried to ");
kpi_log(write ? "write " : "read ");
kpi_lognum(width);
kpi_log(" byte(s) at 0x");
kpi_loghex(a, 16);
if (kpi.io_base && a < kpi.io_base) {
kpi_log(", which is ");
kpi_lognum((long long)(kpi.io_base - a));
kpi_log(" below the mapping");
} else if (kpi.io_base) {
kpi_log(", which is past the end of the mapping");
} else {
kpi_log(", but nothing is mapped yet");
}
kpi_log("\n");
}
return 0;
}
unsigned char
kpi_ioread8(void *addr)
{
if (!kpi_io_ok(addr, 1, 0)) return 0xff;
return *(volatile unsigned char *)addr;
}
unsigned short
kpi_ioread16(void *addr)
{
if (!kpi_io_ok(addr, 2, 0)) return 0xffff;
return *(volatile unsigned short *)addr;
}
unsigned int
kpi_ioread32(void *addr)
{
if (!kpi_io_ok(addr, 4, 0)) return 0xffffffff;
return *(volatile unsigned int *)addr;
}
void
kpi_iowrite8(unsigned char v, void *addr)
{
if (!kpi_io_ok(addr, 1, 1)) return;
*(volatile unsigned char *)addr = v;
}
void
kpi_iowrite16(unsigned short v, void *addr)
{
if (!kpi_io_ok(addr, 2, 1)) return;
*(volatile unsigned short *)addr = v;
}
void
kpi_iowrite32(unsigned int v, void *addr)
{
if (!kpi_io_ok(addr, 4, 1)) return;
*(volatile unsigned int *)addr = v;
}