// SPDX-License-Identifier: Apache-2.0 // // The KPI, and the table the loader resolves against. See ldls/kpi/kpi.h. // // Provenance, once, for every function below: the *signature* of each is a // fact -- a compiled driver calls it with these arguments in these registers // and there is no other way to express that. What each one *does* is written // here from the published behaviour and from what the driver requires of it. // No comment, macro body or implementation has been carried over from // anywhere. docs/plan/ldls.md ยง3. #include "types.h" #include "user.h" #include "fcntl.h" #include "posix.h" #include "pciuser.h" #include "netuser.h" #include "kpi.h" struct kpi_ctx kpi; int kpi_verbose; void kpi_log(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } void kpi_lognum(long long v) { char b[24]; int i = 0; if (v < 0) { write(1, "-", 1); v = -v; } if (v == 0) { write(1, "0", 1); return; } while (v > 0) { b[i++] = (char)('0' + (v % 10)); v /= 10; } while (i-- > 0) write(1, &b[i], 1); } void kpi_loghex(unsigned long long v, int digits) { static const char h[] = "0123456789abcdef"; char b[16]; int i; if (digits > 16) digits = 16; for (i = digits - 1; i >= 0; i--) { b[i] = h[v & 0xF]; v >>= 4; } write(1, b, digits); } static int eq(const char *a, const char *b) { while (*a && *a == *b) { a++; b++; } return *a == 0 && *b == 0; } void * kpi_find(const char *name) { int i; for (i = 0; i < kpi_count; i++) if (eq(kpi_table[i].name, name)) return kpi_table[i].fn; return 0; } // --- memory ------------------------------------------------------------------- // // There is no allocator in lib/, and a driver's allocations are neither many // nor small, so every one of these is an mmap. Nothing is ever freed: a hosted // driver's lifetime is the process's, and the kernel reclaims it all at exit. // That is a leak in the strict sense and the right trade here -- writing an // allocator to serve a program that will be killed to restart it is work spent // on the wrong problem. static void * kpi_alloc(unsigned long n) { void *p; if (n == 0) n = 8; p = mmap(0, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == 0 || (long)p == -1) return 0; return p; // mmap gives zeroed pages, which kzalloc wants } void *kpi_kmalloc(unsigned long n, unsigned int flags) { (void)flags; return kpi_alloc(n); } void kpi_kfree(void *p) { (void)p; } void * kpi_krealloc(void *old, unsigned long n, unsigned int flags) { (void)flags; // Never shrinks and never reuses. A driver that reallocs in its fast path // would notice; none of the ones this hosts do it at all. { unsigned char *p = kpi_alloc(n); unsigned char *o = (unsigned char *)old; unsigned long i; if (p && o) for (i = 0; i < n; i++) p[i] = o[i]; return p; } } // dma_alloc_attrs(dev, size, dma_handle, gfp, attrs) // // The one allocation that cannot be an ordinary mmap: the device is going to // read it by physical address, so it has to come from the kernel's DMA arena. // PCIIOC_DMA is exactly that call, and it reports both addresses because the // physical one is precisely what a process cannot work out for itself. void * kpi_dma_alloc_attrs(void *dev, unsigned long size, unsigned long long *handle, unsigned int gfp, unsigned long attrs) { struct pciuser_dma d; (void)dev; (void)gfp; (void)attrs; d.size = size; if (ioctl(kpi.pcifd, PCIIOC_DMA, &d) < 0) { kpi_log("kpi: no DMA memory for "); kpi_lognum(size); kpi_log(" bytes\n"); return 0; } if (handle) *handle = d.paddr; if (kpi_verbose) { kpi_log("kpi: dma "); kpi_lognum(size); kpi_log(" bytes at phys 0x"); kpi_loghex(d.paddr, 16); kpi_log("\n"); } return (void *)d.uaddr; } void kpi_dma_free_attrs(void *dev, unsigned long size, void *va, unsigned long long handle, unsigned long attrs) { // PCIIOC_DMA has no free; a driver's blocks come back when it exits. See // the note in kernel/dev/pciuser.c. (void)dev; (void)size; (void)va; (void)handle; (void)attrs; } // --- things that only have to not crash --------------------------------------- // // A hosted driver takes locks, starts timers and waits for things. In one // process with no preemption inside the driver, a lock is a no-op that is // *correct* rather than merely convenient: there is nobody to race with. // // This stops being true the moment a driver has an interrupt thread, and the // note in docs/plan/ldls.md about sleeping-context fidelity is about exactly // this. It is the deepest unfixed assumption in the whole layer. void kpi_nop(void) { } int kpi_zero(void) { return 0; } int kpi_one(void) { return 1; } // --- printing ------------------------------------------------------------------ // _printk(fmt, ...) and the netdev/dev variants, which put the format second. // // Linux format strings are printf's with a leading level byte and %p // extensions that expand pointers by kind. Nothing here expands them, so the // string is printed as it stands -- which is honest and is enough for the // lines that matter, the ones a driver prints when it has found its hardware. static void print_fmt(const char *f) { int n = 0; if (!f) return; if ((unsigned char)f[0] == 0x01 && f[1] >= '0' && f[1] <= '7') f += 2; kpi_log(" driver: "); kpi_log(f); while (f[n]) n++; if (n == 0 || f[n - 1] != '\n') kpi_log("\n"); } int kpi_printk(const char *fmt) { print_fmt(fmt); return 0; } int kpi_netdev_print(void *dev, const char *fmt) { (void)dev; print_fmt(fmt); return 0; } // --- the table ----------------------------------------------------------------- // // Names on the left are Linux's and are facts. Everything on the right is ours. extern void *kpi_pci_iomap(void *pdev, int bar, unsigned long max); extern void kpi_pci_iounmap(void *pdev, void *addr); extern int kpi_pci_enable_device(void *pdev); extern int kpi_pci_request_regions(void *pdev, const char *name); extern void kpi_pci_release_regions(void *pdev); extern void kpi_pci_set_master(void *pdev); extern int kpi_pci_read_config_word(void *pdev, int where, unsigned short *v); extern int kpi_pci_write_config_word(void *pdev, int where, unsigned short v); extern unsigned char kpi_ioread8(void *addr); extern unsigned short kpi_ioread16(void *addr); extern unsigned int kpi_ioread32(void *addr); extern void kpi_iowrite8(unsigned char v, void *addr); extern void kpi_iowrite16(unsigned short v, void *addr); extern void kpi_iowrite32(unsigned int v, void *addr); extern void *kpi_alloc_etherdev_mqs(int priv, unsigned int tx, unsigned int rx); extern void kpi_free_netdev(void *dev); extern int kpi_register_netdev(void *dev); extern void kpi_unregister_netdev(void *dev); extern void kpi_dev_addr_mod(void *dev, unsigned int off, const unsigned char *addr, unsigned int len); const struct kpi_sym kpi_table[] = { // PCI { "pci_enable_device", (void *)kpi_pci_enable_device }, { "pci_disable_device", (void *)kpi_nop }, { "pci_request_regions", (void *)kpi_pci_request_regions }, { "pci_release_regions", (void *)kpi_pci_release_regions }, { "pci_set_master", (void *)kpi_pci_set_master }, { "pci_iomap", (void *)kpi_pci_iomap }, { "pci_iounmap", (void *)kpi_pci_iounmap }, { "pci_read_config_word", (void *)kpi_pci_read_config_word }, { "pci_write_config_word", (void *)kpi_pci_write_config_word }, // Registers { "ioread8", (void *)kpi_ioread8 }, { "ioread16", (void *)kpi_ioread16 }, { "ioread32", (void *)kpi_ioread32 }, { "iowrite8", (void *)kpi_iowrite8 }, { "iowrite16", (void *)kpi_iowrite16 }, { "iowrite32", (void *)kpi_iowrite32 }, // Memory { "__kmalloc_noprof", (void *)kpi_kmalloc }, { "kmalloc_noprof", (void *)kpi_kmalloc }, { "kfree", (void *)kpi_kfree }, { "krealloc_noprof", (void *)kpi_krealloc }, { "dma_alloc_attrs", (void *)kpi_dma_alloc_attrs }, { "dma_free_attrs", (void *)kpi_dma_free_attrs }, // Network { "alloc_etherdev_mqs", (void *)kpi_alloc_etherdev_mqs }, { "free_netdev", (void *)kpi_free_netdev }, { "register_netdev", (void *)kpi_register_netdev }, { "unregister_netdev", (void *)kpi_unregister_netdev }, { "dev_addr_mod", (void *)kpi_dev_addr_mod }, // Printing { "_printk", (void *)kpi_printk }, { "netdev_info", (void *)kpi_netdev_print }, { "netdev_err", (void *)kpi_netdev_print }, { "_dev_info", (void *)kpi_netdev_print }, { "_dev_err", (void *)kpi_netdev_print }, // Things a driver calls at probe that need only to return sensibly. Each is // here because 8139too calls it and because doing nothing is the right // answer in one process -- see the note above kpi_nop(). { "mutex_lock", (void *)kpi_nop }, { "mutex_unlock", (void *)kpi_nop }, { "__mutex_init", (void *)kpi_nop }, { "_raw_spin_lock", (void *)kpi_nop }, { "_raw_spin_unlock", (void *)kpi_nop }, { "_raw_spin_lock_irq", (void *)kpi_nop }, { "_raw_spin_unlock_irq", (void *)kpi_nop }, { "_raw_spin_lock_bh", (void *)kpi_nop }, { "_raw_spin_unlock_bh", (void *)kpi_nop }, { "_raw_spin_lock_irqsave", (void *)kpi_zero }, { "_raw_spin_unlock_irqrestore", (void *)kpi_nop }, { "timer_init_key", (void *)kpi_nop }, { "netif_napi_add_weight_locked", (void *)kpi_nop }, { "__const_udelay", (void *)kpi_nop }, { "synchronize_rcu", (void *)kpi_nop }, }; const int kpi_count = (int)(sizeof(kpi_table) / sizeof(kpi_table[0]));