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>
208 lines
6.5 KiB
C
208 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// The PCI half of the KPI. See ldls/kpi/kpi.h for the provenance rule.
|
|
//
|
|
// Every one of these lands on `/dev/pci`, which is the interface
|
|
// docs/plan/hybrid.md built and `vgpud` proved. That it fits a foreign driver
|
|
// without alteration is the point the hybrid plan was making when it insisted
|
|
// the interface be about *PCI devices* rather than about a driver model:
|
|
//
|
|
// "an interface whose first consumers are native drivers is a general
|
|
// mechanism that something foreign can use later, rather than a hole shaped
|
|
// like something foreign."
|
|
//
|
|
// This file is that claim being collected.
|
|
|
|
#include "types.h"
|
|
#include "user.h"
|
|
#include "fcntl.h"
|
|
#include "posix.h"
|
|
#include "pciuser.h"
|
|
#include "kpi.h"
|
|
|
|
// The PCI resource flag bits, which are ABI between the kernel and every
|
|
// driver written against it. Not layout, so BTF does not carry them.
|
|
#define LDLS_IORESOURCE_IO 0x00000100
|
|
#define LDLS_IORESOURCE_MEM 0x00000200
|
|
|
|
// The device is claimed by the host before the driver runs, because claiming
|
|
// is what enables memory space and bus mastering -- and a BAR read before that
|
|
// can legitimately be zero. So by the time a driver calls this, it has already
|
|
// happened, and the honest answer is success.
|
|
int
|
|
kpi_pci_enable_device(void *pdev)
|
|
{
|
|
(void)pdev;
|
|
return 0;
|
|
}
|
|
|
|
// Regions are not reserved against anyone: this process holds the whole
|
|
// device, exclusively, because /dev/pci gives out one device per process.
|
|
// There is nobody to arbitrate with.
|
|
int
|
|
kpi_pci_request_regions(void *pdev, const char *name)
|
|
{
|
|
(void)pdev; (void)name;
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
kpi_pci_release_regions(void *pdev)
|
|
{
|
|
(void)pdev;
|
|
}
|
|
|
|
// Also already done, by PCIIOC_CLAIM, which sets PCI_CMD_MEMORY and
|
|
// PCI_CMD_BUS_MASTER together -- see do_claim() in kernel/dev/pciuser.c, and
|
|
// the comment there about why a driver that has to ask separately will forget.
|
|
void
|
|
kpi_pci_set_master(void *pdev)
|
|
{
|
|
(void)pdev;
|
|
}
|
|
|
|
int
|
|
kpi_pci_read_config_word(void *pdev, int where, unsigned short *v)
|
|
{
|
|
struct pciuser_cfg c;
|
|
|
|
(void)pdev;
|
|
c.index = (unsigned int)kpi.index;
|
|
c.offset = (unsigned int)where & ~3u;
|
|
c.value = 0;
|
|
if (ioctl(kpi.pcifd, PCIIOC_CFG, &c) < 0)
|
|
return -1;
|
|
if (v)
|
|
*v = (unsigned short)(c.value >> ((where & 2) * 8));
|
|
return 0;
|
|
}
|
|
|
|
// Refused, deliberately and loudly.
|
|
//
|
|
// /dev/pci has no config *write* at all, and that is the line that matters:
|
|
// a write is how a BAR gets moved out from under the kernel. A driver that
|
|
// needs one has found a real limit of this interface rather than a bug, and
|
|
// silently succeeding would turn that into a mystery somewhere else.
|
|
int
|
|
kpi_pci_write_config_word(void *pdev, int where, unsigned short v)
|
|
{
|
|
(void)pdev; (void)v;
|
|
kpi_log("kpi: the driver tried to WRITE config space at 0x");
|
|
kpi_loghex((unsigned long long)where, 2);
|
|
kpi_log(" -- /dev/pci does not allow that\n");
|
|
return -1;
|
|
}
|
|
|
|
// Map a BAR.
|
|
//
|
|
// The offset passed to mmap is the BAR's *physical address*, which is what
|
|
// makes the kernel able to check it: an offset that is not inside a BAR of a
|
|
// device this process holds is refused, and that check is the whole difference
|
|
// between /dev/pci and /dev/mem with extra steps.
|
|
void *
|
|
kpi_pci_iomap(void *pdev, int bar, unsigned long max)
|
|
{
|
|
unsigned long long len;
|
|
void *p;
|
|
|
|
(void)pdev;
|
|
if (bar < 0 || bar >= 6 || kpi.bar_base[bar] == 0) {
|
|
kpi_log("kpi: pci_iomap of BAR "); kpi_lognum(bar);
|
|
kpi_log(", which this device does not have\n");
|
|
return 0;
|
|
}
|
|
|
|
// An I/O-space BAR has no address to map. rtl8139 offers both and its driver
|
|
// prefers MMIO when it can get it; saying no here is what makes it take that
|
|
// path rather than issuing port instructions a process cannot execute.
|
|
if (kpi.bar_flags[bar] & PCIUSER_BAR_IO) {
|
|
kpi_log("kpi: BAR "); kpi_lognum(bar);
|
|
kpi_log(" is I/O space, which cannot be mapped\n");
|
|
return 0;
|
|
}
|
|
|
|
len = kpi.bar_size[bar];
|
|
if (max && max < len)
|
|
len = max;
|
|
|
|
p = mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, kpi.pcifd,
|
|
kpi.bar_base[bar]);
|
|
|
|
// Any small negative is an error, not an address.
|
|
//
|
|
// SchOS returns -errno from mmap rather than MAP_FAILED, so checking only
|
|
// for -1 lets every other failure through as a pointer. That is not a
|
|
// theoretical worry: this returned -EACCES, the driver took -13 as its
|
|
// register base, and the first write landed at 0x4e -- thirteen below where
|
|
// it meant to be, which is how the real fault below was eventually found.
|
|
if (p == 0 || ((long)p < 0 && (long)p > -4096)) {
|
|
kpi_log("kpi: the kernel refused to map BAR "); kpi_lognum(bar);
|
|
kpi_log(", error "); kpi_lognum(-(long)p); kpi_log("\n");
|
|
return 0;
|
|
}
|
|
|
|
kpi.io_base = (unsigned long)p;
|
|
kpi.io_len = len;
|
|
|
|
kpi_log(" kpi: mapped BAR "); kpi_lognum(bar);
|
|
kpi_log(" -- phys 0x"); kpi_loghex(kpi.bar_base[bar], 16);
|
|
kpi_log(", "); kpi_lognum((long long)len); kpi_log(" bytes\n");
|
|
return p;
|
|
}
|
|
|
|
// pci_ioremap_bar(pdev, bar) -- the same thing as pci_iomap with no cap. A
|
|
// second spelling, which is the sort of thing a second driver turns up.
|
|
void *
|
|
kpi_pci_ioremap_bar(void *pdev, int bar)
|
|
{
|
|
return kpi_pci_iomap(pdev, bar, 0);
|
|
}
|
|
|
|
// pci_select_bars(pdev, flags) -- which BARs match a resource kind.
|
|
//
|
|
// A driver asks this before requesting regions, so the answer decides which
|
|
// BARs it thinks exist. Reporting one that is not there gets it refused later;
|
|
// omitting one it needs gets it to give up here.
|
|
int
|
|
kpi_pci_select_bars(void *pdev, unsigned long flags)
|
|
{
|
|
int bars = 0, b;
|
|
|
|
(void)pdev;
|
|
for (b = 0; b < 6; b++) {
|
|
if (!kpi.bar_base[b])
|
|
continue;
|
|
if ((kpi.bar_flags[b] & PCIUSER_BAR_IO) && (flags & LDLS_IORESOURCE_IO))
|
|
bars |= 1 << b;
|
|
if ((kpi.bar_flags[b] & PCIUSER_BAR_MMIO) && (flags & LDLS_IORESOURCE_MEM))
|
|
bars |= 1 << b;
|
|
}
|
|
return bars;
|
|
}
|
|
|
|
int
|
|
kpi_pci_request_selected_regions(void *pdev, int bars, const char *name)
|
|
{
|
|
(void)pdev; (void)bars; (void)name;
|
|
return 0; // nobody to arbitrate with; see above
|
|
}
|
|
|
|
void
|
|
kpi_pci_release_selected_regions(void *pdev, int bars)
|
|
{
|
|
(void)pdev; (void)bars;
|
|
}
|
|
|
|
// Saving and restoring config space is what a driver does around a reset it
|
|
// expects to clear the BARs. LDLS never resets the device at that level and
|
|
// has no config write to restore with, so there is nothing to save -- and
|
|
// saying so by succeeding is right, because a driver that cannot save state
|
|
// refuses to probe at all.
|
|
int kpi_pci_save_state(void *pdev) { (void)pdev; return 0; }
|
|
void kpi_pci_restore_state(void *pdev) { (void)pdev; }
|
|
|
|
void
|
|
kpi_pci_iounmap(void *pdev, void *addr)
|
|
{
|
|
(void)pdev; (void)addr;
|
|
}
|