// 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; }