SchOS/ldls/kpi/base.c
Nexoniarz 3676036762 Netlink datagrams can be measured, credentials name their sender
Epiphany's main thread spun at 100% on GLib's network monitor. GLib
sizes each netlink datagram with recvmsg(MSG_PEEK|MSG_TRUNC) and no
buffer; that answered 0, GLib read 0 bytes, the answer stayed queued and
poll() said readable again, forever. recvmsg on netlink now peeks
without consuming and with MSG_TRUNC returns what is waiting. A drained
netlink socket also no longer reports hangup -- it has no peer, and
sock_hup took that for a closed one.

SCM_CREDENTIALS under SO_PASSCRED named the process that created the far
end, not the one that wrote to it. For a socketpair those differ: WebKit
makes the pair, hands one end to a child, and learns the child's pid
from the credentials on its first byte. Each end now records its last
writer.

LDLS: napi_consume_skb frees the skb. r8169 frees every sent frame with
it, and as a stub it leaked one per frame until the DMA heap ran out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 14:58:14 +01:00

968 lines
31 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
//
// The rest of the kernel a modern driver stands on: the driver core, device-
// managed resources, printf, time, bitmaps, firmware, and the data symbols.
// See ldls/kpi/kpi.h.
//
// r8169 is the reason for most of it. Unlike 8139too and e1000 it does not
// talk to its PHY itself: it registers an MDIO bus with libphy, and libphy
// finds the PHY on it and binds a PHY driver (realtek.ko) through Linux's
// driver core -- bus_type.match, then device_driver.probe. r8169 then checks
// that a dedicated PHY driver was bound and refuses the device if not. So the
// driver core here is not decoration; it is what makes the device usable.
//
// Several functions below exist because a stub's zero is a lie in their
// case, and each says which lie it was:
// __list_add_valid_or_report 0 = "corrupt list", and with CONFIG_DEBUG_LIST
// every list_add asks, so nothing is ever added;
// try_module_get 0 = "module going away", phylib refuses the PHY;
// dev_err_probe returns its error, or failure looks like success;
// eth_platform_get_mac_address 0 = "here is your MAC", all zeroes;
// firmware_request_nowarn 0 = "loaded", with a NULL firmware;
// __bitmap_* phylib computes link modes with them;
// __const_udelay a delay of nothing turns every poll-with-
// timeout into an immediate timeout.
#include "core/types.h"
#include "uapi/user.h"
#include "uapi/fcntl.h"
#include "uapi/posix.h"
#include "kpi.h"
typedef __builtin_va_list va_list;
#define va_start(a, l) __builtin_va_start(a, l)
#define va_end(a) __builtin_va_end(a)
#define va_arg(a, t) __builtin_va_arg(a, t)
extern void *kpi_kmalloc(unsigned long n, unsigned int flags);
extern int kpi_pci_enable_device(void *pdev);
extern void *kpi_pci_iomap(void *pdev, int bar, unsigned long max);
extern int kpi_pci_read_config_word(void *pdev, int where, unsigned short *v);
extern void *kpi_alloc_etherdev_mqs(int priv, unsigned int tx, unsigned int rx);
extern void *kpi_dma_alloc_attrs(void *dev, unsigned long size,
unsigned long long *handle, unsigned int gfp,
unsigned long attrs);
extern void *kpi_alloc_pages(unsigned int gfp, unsigned int order);
static void *zalloc(unsigned long n) { return kpi_kmalloc(n, 0); }
static unsigned long rdp(const void *base, unsigned int off)
{ unsigned long v; __builtin_memcpy(&v, (const char *)base + off, 8); return v; }
static void wrp(void *base, unsigned int off, unsigned long v)
{ __builtin_memcpy((char *)base + off, &v, 8); }
static int slen(const char *s) { int n = 0; while (s && s[n]) n++; return n; }
// --- printf ---------------------------------------------------------------------
//
// Linux's format language: C's, plus %p followed by letters that say what the
// pointer is. Of those, only the MAC address (%pM) is worth expanding here;
// the rest print as a pointer and consume their letters, as Linux does, so
// that the text after them is not mistaken for part of the format.
struct out { char *buf; unsigned long cap, n; };
static void put(struct out *o, char c)
{
if (o->n + 1 < o->cap)
o->buf[o->n] = c;
o->n++;
}
static void
putnum(struct out *o, unsigned long long v, int base, int neg, int width,
int zero, int left, int upper, int prec)
{
char tmp[24];
const char *dig = upper ? "0123456789ABCDEF" : "0123456789abcdef";
int n = 0, len, pad;
if (v == 0 && prec != 0)
tmp[n++] = '0';
while (v) { tmp[n++] = dig[v % (unsigned)base]; v /= (unsigned)base; }
while (n < prec && n < (int)sizeof(tmp)) tmp[n++] = '0';
len = n + (neg ? 1 : 0);
pad = width > len ? width - len : 0;
if (!left && !zero) while (pad-- > 0) put(o, ' ');
if (neg) put(o, '-');
if (!left && zero) while (pad-- > 0) put(o, '0');
while (n > 0) put(o, tmp[--n]);
if (left) while (pad-- > 0) put(o, ' ');
}
int
kpi_vsnprintf(char *buf, unsigned long cap, const char *f, va_list ap)
{
struct out o = { buf, cap, 0 };
if (!f) f = "";
while (*f) {
if (*f != '%') { put(&o, *f++); continue; }
f++;
int left = 0, zero = 0, width = 0, prec = -1, lng = 0;
for (;; f++) {
if (*f == '-') left = 1;
else if (*f == '0') zero = 1;
else if (*f == '+' || *f == ' ' || *f == '#') ;
else break;
}
if (*f == '*') { width = va_arg(ap, int); f++; }
else while (*f >= '0' && *f <= '9') width = width * 10 + (*f++ - '0');
if (*f == '.') {
f++; prec = 0;
if (*f == '*') { prec = va_arg(ap, int); f++; }
else while (*f >= '0' && *f <= '9') prec = prec * 10 + (*f++ - '0');
}
for (;; f++) {
if (*f == 'l') lng++;
else if (*f == 'z' || *f == 't' || *f == 'j') lng = 2;
else if (*f == 'h') ;
else break;
}
char c = *f ? *f++ : 0;
switch (c) {
case 'd': case 'i': {
long long v = lng ? va_arg(ap, long long) : va_arg(ap, int);
int neg = v < 0;
putnum(&o, neg ? (unsigned long long)-v : (unsigned long long)v, 10,
neg, width, zero, left, 0, prec);
break;
}
case 'u': case 'x': case 'X': case 'o': {
unsigned long long v = lng ? va_arg(ap, unsigned long long)
: va_arg(ap, unsigned int);
putnum(&o, v, c == 'u' ? 10 : c == 'o' ? 8 : 16, 0, width, zero, left,
c == 'X', prec);
break;
}
case 'c':
put(&o, (char)va_arg(ap, int));
break;
case 's': {
const char *s = va_arg(ap, const char *);
int n, pad;
if (!s) s = "(null)";
n = slen(s);
if (prec >= 0 && n > prec) n = prec;
pad = width > n ? width - n : 0;
if (!left) while (pad-- > 0) put(&o, ' ');
for (int i = 0; i < n; i++) put(&o, s[i]);
if (left) while (pad-- > 0) put(&o, ' ');
break;
}
case 'p': {
const unsigned char *p = va_arg(ap, const unsigned char *);
if ((*f == 'M' || *f == 'm') && p) {
for (int i = 0; i < 6; i++) {
if (i && *f == 'M') put(&o, ':');
putnum(&o, p[i], 16, 0, 2, 1, 0, 0, -1);
}
} else if (*f == 'e') {
put(&o, 'E');
long e = (long)p;
putnum(&o, (unsigned long long)(e < 0 ? -e : e), 10, e < 0, 0, 0, 0, 0, -1);
} else {
put(&o, '0'); put(&o, 'x');
putnum(&o, (unsigned long)p, 16, 0, 0, 0, 0, 0, -1);
}
while ((*f >= 'a' && *f <= 'z') || (*f >= 'A' && *f <= 'Z') ||
(*f >= '0' && *f <= '9'))
f++;
break;
}
case '%':
put(&o, '%');
break;
default:
break;
}
}
if (cap)
buf[o.n < cap ? o.n : cap - 1] = 0;
return (int)o.n;
}
int
kpi_snprintf(char *buf, unsigned long cap, const char *f, ...)
{
va_list ap;
va_start(ap, f);
int n = kpi_vsnprintf(buf, cap, f, ap);
va_end(ap);
return n;
}
int
kpi_sprintf(char *buf, const char *f, ...)
{
va_list ap;
va_start(ap, f);
int n = kpi_vsnprintf(buf, 1UL << 20, f, ap);
va_end(ap);
return n;
}
char *
kpi_kvasprintf(const char *f, va_list ap)
{
char tmp[512];
va_list ap2;
__builtin_va_copy(ap2, ap);
int n = kpi_vsnprintf(tmp, sizeof(tmp), f, ap2);
va_end(ap2);
char *s = zalloc((unsigned long)n + 1);
if (s)
kpi_vsnprintf(s, (unsigned long)n + 1, f, ap);
return s;
}
char *
kpi_kasprintf(unsigned int gfp, const char *f, ...)
{
(void)gfp;
va_list ap;
va_start(ap, f);
char *s = kpi_kvasprintf(f, ap);
va_end(ap);
return s;
}
// A kernel message: the level byte off the front, the driver's text after a
// prefix that says whose it is.
static void
emit(const char *who, const char *f, va_list ap)
{
char line[512];
if (f && (unsigned char)f[0] == 0x01 && f[1])
f += 2;
kpi_vsnprintf(line, sizeof(line), f, ap);
kpi_log(" ");
if (who && *who) { kpi_log(who); kpi_log(": "); }
kpi_log(line);
int n = slen(line);
if (n == 0 || line[n - 1] != '\n')
kpi_log("\n");
}
static const char *
dev_name_of(void *dev)
{
if (!dev || !kpi_l.dev_kobj_ok)
return "";
const char *n = (const char *)rdp(dev, kpi_l.dev_kobj + kpi_l.kobj_name);
return n ? n : "";
}
int kpi_vprintk(const char *f, va_list ap) { emit(0, f, ap); return 0; }
int
kpi_printk_fmt(const char *f, ...)
{
va_list ap; va_start(ap, f); emit(0, f, ap); va_end(ap); return 0;
}
void
kpi_dev_printk(void *dev, const char *f, ...)
{
va_list ap; va_start(ap, f); emit(dev_name_of(dev), f, ap); va_end(ap);
}
// netdev_* take the net_device, whose name is not kept anywhere this can
// read; "ldls0" is what the interface is called on this side anyway.
void
kpi_netdev_printk_fmt(void *nd, const char *f, ...)
{
(void)nd;
va_list ap; va_start(ap, f); emit("ldls0", f, ap); va_end(ap);
}
void
kpi_netdev_printk_lvl(const char *lvl, void *nd, const char *f, ...)
{
(void)lvl; (void)nd;
va_list ap; va_start(ap, f); emit("ldls0", f, ap); va_end(ap);
}
int
kpi_dev_err_probe(void *dev, int err, const char *f, ...)
{
va_list ap; va_start(ap, f); emit(dev_name_of(dev), f, ap); va_end(ap);
return err;
}
void
kpi_warn_printk(const char *f, ...)
{
kpi_log(" WARNING ");
va_list ap; va_start(ap, f); emit(0, f, ap); va_end(ap);
}
// --- strings ----------------------------------------------------------------------
int
kpi_strcmp(const char *a, const char *b)
{
while (*a && *a == *b) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
char *
kpi_strcpy(char *d, const char *s)
{
char *r = d;
while ((*d++ = *s++)) ;
return r;
}
const char *
kpi_kstrdup_const(const char *s, unsigned int gfp)
{
(void)gfp;
if (!s) return 0;
char *d = zalloc((unsigned long)slen(s) + 1);
if (d) kpi_strcpy(d, s);
return d;
}
int
kpi_kstrtoull(const char *s, unsigned int base, unsigned long long *res)
{
unsigned long long v = 0;
int any = 0;
if (base == 0) {
base = 10;
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; }
else if (s[0] == '0') base = 8;
}
for (; *s && *s != '\n'; s++) {
int d = (*s >= '0' && *s <= '9') ? *s - '0' :
(*s >= 'a' && *s <= 'f') ? *s - 'a' + 10 :
(*s >= 'A' && *s <= 'F') ? *s - 'A' + 10 : 99;
if (d >= (int)base) return -22;
v = v * base + (unsigned)d;
any = 1;
}
if (!any) return -22;
*res = v;
return 0;
}
// --- time --------------------------------------------------------------------------
//
// HZ is a compile-time constant in the module. It is 1000 on the kernels this
// has been built against, and jiffies has to count at the module's HZ or
// every delay it computes in jiffies is wrong by the ratio -- phylib's
// one-second link poll became ten seconds at the old 100.
static unsigned long long
now_ns(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return (unsigned long long)uptime() * 10000000ULL;
return (unsigned long long)ts.tv_sec * 1000000000ULL + (unsigned long long)ts.tv_nsec;
}
unsigned long long kpi_ktime_get(void) { return now_ns(); }
unsigned long
kpi_jiffies_ms(void)
{
return (unsigned long)(now_ns() / 1000000ULL);
}
static void
spin_us(unsigned long us)
{
unsigned long long end = now_ns() + (unsigned long long)us * 1000ULL;
while (now_ns() < end)
__builtin_ia32_pause();
}
static void
sleep_us(unsigned long us)
{
// Below a scheduler tick, a sleep would round up to the tick; the waits
// that short are MDIO and register polls, where that would be a hundredfold.
if (us < 10000) {
spin_us(us);
return;
}
struct timespec ts = { (long)(us / 1000000), (long)(us % 1000000) * 1000 };
nanosleep(&ts);
}
// udelay(n) arrives as __const_udelay(n * 0x10c7): microseconds scaled by
// 2^32 / 10^6.
void kpi_const_udelay(unsigned long xloops) { spin_us((unsigned long)(((unsigned long long)xloops * 1000000ULL) >> 32) + 1); }
void kpi_udelay(unsigned long us) { spin_us(us); }
void kpi_ndelay(unsigned long ns) { spin_us(ns / 1000 + 1); }
void kpi_msleep(unsigned int ms) { sleep_us((unsigned long)ms * 1000); }
void kpi_usleep_range(unsigned long min, unsigned long max, unsigned int st)
{ (void)max; (void)st; sleep_us(min); }
// --- bitmaps -------------------------------------------------------------------------
//
// phylib keeps link modes in 121-bit maps, past the size Linux inlines, so
// these are called for every "what can both ends do" question it asks.
#define WORDS(n) (((n) + 63) / 64)
static unsigned long
lastmask(unsigned int nbits)
{
return (nbits % 64) ? (1UL << (nbits % 64)) - 1 : ~0UL;
}
int
kpi_bitmap_and(unsigned long *d, const unsigned long *a, const unsigned long *b,
unsigned int nbits)
{
unsigned long any = 0;
unsigned int w = WORDS(nbits);
for (unsigned int i = 0; i < w; i++) {
d[i] = a[i] & b[i];
any |= (i == w - 1) ? (d[i] & lastmask(nbits)) : d[i];
}
return any != 0;
}
int
kpi_bitmap_andnot(unsigned long *d, const unsigned long *a,
const unsigned long *b, unsigned int nbits)
{
unsigned long any = 0;
unsigned int w = WORDS(nbits);
for (unsigned int i = 0; i < w; i++) {
d[i] = a[i] & ~b[i];
any |= (i == w - 1) ? (d[i] & lastmask(nbits)) : d[i];
}
return any != 0;
}
void
kpi_bitmap_or(unsigned long *d, const unsigned long *a, const unsigned long *b,
unsigned int nbits)
{
for (unsigned int i = 0; i < WORDS(nbits); i++)
d[i] = a[i] | b[i];
}
int
kpi_bitmap_equal(const unsigned long *a, const unsigned long *b,
unsigned int nbits)
{
unsigned int w = WORDS(nbits);
for (unsigned int i = 0; i < w; i++) {
unsigned long m = (i == w - 1) ? lastmask(nbits) : ~0UL;
if ((a[i] ^ b[i]) & m) return 0;
}
return 1;
}
int
kpi_bitmap_intersects(const unsigned long *a, const unsigned long *b,
unsigned int nbits)
{
unsigned int w = WORDS(nbits);
for (unsigned int i = 0; i < w; i++) {
unsigned long m = (i == w - 1) ? lastmask(nbits) : ~0UL;
if (a[i] & b[i] & m) return 1;
}
return 0;
}
unsigned long
kpi_find_first_bit(const unsigned long *addr, unsigned long size)
{
for (unsigned long i = 0; i < size; i++)
if (addr[i / 64] & (1UL << (i % 64)))
return i;
return size;
}
// --- the driver core -----------------------------------------------------------------
//
// Enough of it to bind a driver to a device on a bus, which is all libphy
// asks of it: drivers and devices are remembered, and whenever one arrives it
// is offered to the other side -- bus_type.match decides, and the bus's probe
// or else the driver's runs with device.driver already set, as Linux does.
// Nothing is reference-counted and nothing is ever torn down; see the note on
// memory in kpi.c.
#define NCORE 64
static void *core_drv[NCORE];
static int core_ndrv;
static void *core_dev[NCORE];
static int core_ndev;
static int
core_bind(void *dev, void *drv)
{
void *bus = (void *)rdp(dev, kpi_l.dev_bus);
int (*match)(void *, void *) = bus ? (int (*)(void *, void *))rdp(bus, kpi_l.bus_match) : 0;
int (*bprobe)(void *) = bus ? (int (*)(void *))rdp(bus, kpi_l.bus_probe) : 0;
int (*dprobe)(void *) = (int (*)(void *))rdp(drv, kpi_l.drv_probe);
if (match && match(dev, drv) <= 0)
return 0;
wrp(dev, kpi_l.dev_driver, (unsigned long)drv);
int r = bprobe ? bprobe(dev) : dprobe ? dprobe(dev) : 0;
const char *dn = (const char *)rdp(drv, kpi_l.drv_name);
kpi_log(" kpi: bound "); kpi_log(dn ? dn : "a driver");
kpi_log(" to "); kpi_log(dev_name_of(dev));
if (r != 0) {
kpi_log(" -- probe said "); kpi_lognum(r); kpi_log(", unbound\n");
wrp(dev, kpi_l.dev_driver, 0);
return 0;
}
kpi_log("\n");
return 1;
}
static void
core_offer_dev(void *dev)
{
void *bus;
if (!kpi_l.core_ok || rdp(dev, kpi_l.dev_driver))
return;
bus = (void *)rdp(dev, kpi_l.dev_bus);
if (!bus)
return;
for (int i = 0; i < core_ndrv; i++)
if ((void *)rdp(core_drv[i], kpi_l.drv_bus) == bus && core_bind(dev, core_drv[i]))
return;
}
int
kpi_driver_register(void *drv)
{
if (core_ndrv < NCORE)
core_drv[core_ndrv++] = drv;
if (!kpi_l.core_ok)
return 0;
void *bus = (void *)rdp(drv, kpi_l.drv_bus);
for (int i = 0; i < core_ndev; i++)
if (!rdp(core_dev[i], kpi_l.dev_driver) &&
(void *)rdp(core_dev[i], kpi_l.dev_bus) == bus)
core_bind(core_dev[i], drv);
return 0;
}
void
kpi_driver_unregister(void *drv)
{
for (int i = 0; i < core_ndrv; i++)
if (core_drv[i] == drv) core_drv[i] = core_drv[--core_ndrv];
}
int
kpi_device_add(void *dev)
{
int i;
for (i = 0; i < core_ndev; i++)
if (core_dev[i] == dev) break;
if (i == core_ndev && core_ndev < NCORE)
core_dev[core_ndev++] = dev;
core_offer_dev(dev);
return 0;
}
void
kpi_device_del(void *dev)
{
for (int i = 0; i < core_ndev; i++)
if (core_dev[i] == dev) core_dev[i] = core_dev[--core_ndev];
}
void kpi_device_release_driver(void *dev)
{ if (kpi_l.core_ok) wrp(dev, kpi_l.dev_driver, 0); }
void *kpi_get_device(void *dev) { return dev; }
int
kpi_dev_set_name(void *dev, const char *f, ...)
{
if (!kpi_l.dev_kobj_ok)
return 0;
va_list ap;
va_start(ap, f);
char *s = kpi_kvasprintf(f, ap);
va_end(ap);
if (!s)
return -12;
wrp(dev, kpi_l.dev_kobj + kpi_l.kobj_name, (unsigned long)s);
return 0;
}
const char *
kpi_dev_driver_string(void *dev)
{
if (kpi_l.core_ok && dev) {
void *drv = (void *)rdp(dev, kpi_l.dev_driver);
if (drv) {
const char *n = (const char *)rdp(drv, kpi_l.drv_name);
if (n) return n;
}
}
return "";
}
// --- device-managed resources ----------------------------------------------------------
//
// Freed when the device goes, which here is never. What has to be real is
// devres_find: mdio_devres registers a bus only after finding the record
// devm_mdiobus_alloc made, and says -EINVAL otherwise.
#define NRES 128
static struct { void *dev, *release, *data; } res[NRES];
static int nres;
void *
kpi_devres_alloc(void *release, unsigned long size, unsigned int gfp, int nid,
const char *name)
{
(void)gfp; (void)nid; (void)name;
void *d = zalloc(size ? size : 8);
if (d && nres < NRES) {
res[nres].dev = 0; res[nres].release = release; res[nres].data = d;
nres++;
}
return d;
}
void
kpi_devres_add(void *dev, void *data)
{
for (int i = 0; i < nres; i++)
if (res[i].data == data) { res[i].dev = dev; return; }
}
void *
kpi_devres_find(void *dev, void *release, int (*match)(void *, void *, void *),
void *mdata)
{
for (int i = 0; i < nres; i++)
if (res[i].dev == dev && res[i].release == release &&
(!match || match(dev, res[i].data, mdata)))
return res[i].data;
return 0;
}
void
kpi_devres_free(void *data)
{
for (int i = 0; i < nres; i++)
if (res[i].data == data) { res[i] = res[--nres]; return; }
}
void *kpi_devm_kmalloc(void *dev, unsigned long size, unsigned int gfp)
{ (void)dev; (void)gfp; return zalloc(size); }
void *kpi_devm_alloc_etherdev(void *dev, int priv, unsigned int tx, unsigned int rx)
{ (void)dev; return kpi_alloc_etherdev_mqs(priv, tx, rx); }
void *
kpi_dmam_alloc_attrs(void *dev, unsigned long size, unsigned long long *h,
unsigned int gfp, unsigned long attrs)
{ return kpi_dma_alloc_attrs(dev, size, h, gfp, attrs); }
// --- PCI, the managed and newer calls ----------------------------------------------------
int kpi_pcim_enable_device(void *pdev) { return kpi_pci_enable_device(pdev); }
void *
kpi_pcim_iomap_region(void *pdev, int bar, const char *name)
{
(void)name;
void *p = kpi_pci_iomap(pdev, bar, 0);
return p ? p : (void *)-12L; // ERR_PTR(-ENOMEM); NULL is not an error
}
// One vector, and its number is whatever request_irq is then given back --
// the handler is called from the poll loop either way. See kpi_request_irq.
int kpi_pci_alloc_irq_vectors(void *pdev, unsigned int min, unsigned int max,
unsigned int flags)
{ (void)pdev; (void)min; (void)max; (void)flags; return 1; }
int kpi_pci_irq_vector(void *pdev, unsigned int nr) { (void)pdev; (void)nr; return 16; }
int
kpi_pci_read_config_byte(void *pdev, int where, unsigned char *v)
{
unsigned short w = 0;
int r = kpi_pci_read_config_word(pdev, where & ~1, &w);
if (v) *v = (unsigned char)(w >> ((where & 1) * 8));
return r;
}
// /dev/pci has no config write (see pci.c). The ones r8169 makes are tuning,
// and the ASPM change it wants is made by the kernel at boot instead.
int
kpi_pci_write_config_byte(void *pdev, int where, unsigned char v)
{
(void)pdev; (void)v;
kpi_log(" kpi: config write to 0x"); kpi_loghex((unsigned)where, 2);
kpi_log(" not made\n");
return 0;
}
void *kpi_alloc_pages_node(unsigned int gfp, unsigned int order, int nid, void *mask)
{ (void)nid; (void)mask; return kpi_alloc_pages(gfp, order); }
// --- work queues, the rest of the API ----------------------------------------------------
static char kpi_wq_dummy[64];
void *kpi_system_wq = kpi_wq_dummy; // the *variable* is the symbol
void *kpi_alloc_workqueue(const char *f, unsigned int flags, int max, ...)
{ (void)f; (void)flags; (void)max; return kpi_wq_dummy; }
int
kpi_mod_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay)
{
kpi_queue_delayed_work_on(cpu, q, dwork, delay);
return 0;
}
int kpi_disable_work_sync(void *w) { kpi_cancel_work_sync(w); return 0; }
// --- firmware ------------------------------------------------------------------------------
//
// From /lib/firmware, which is where Alpine's linux-firmware-* packages put
// it. struct firmware's layout comes from BTF like everything else.
int
kpi_request_firmware(void **out, const char *name, void *dev)
{
char path[256];
(void)dev;
if (out) *out = 0;
if (!kpi_l.fw_ok || !name)
return -2;
kpi_snprintf(path, sizeof(path), "/lib/firmware/%s", name);
int fd = open(path, O_RDONLY);
if (fd < 0) {
kpi_log(" kpi: no firmware "); kpi_log(path); kpi_log("\n");
return -2;
}
unsigned long cap = 4UL << 20, n = 0;
unsigned char *buf = zalloc(cap);
if (!buf) { close(fd); return -12; }
for (;;) {
int r = read(fd, buf + n, (int)(cap - n > 65536 ? 65536 : cap - n));
if (r <= 0) break;
n += (unsigned long)r;
if (n == cap) break;
}
close(fd);
void *fw = zalloc(kpi_l.fw_size_of ? kpi_l.fw_size_of : 64);
if (!fw) return -12;
wrp(fw, kpi_l.fw_size, n);
wrp(fw, kpi_l.fw_data, (unsigned long)buf);
if (out) *out = fw;
kpi_log(" kpi: firmware "); kpi_log(path); kpi_log(", ");
kpi_lognum((long long)n); kpi_log(" bytes\n");
return 0;
}
// --- small things that must not answer zero ------------------------------------------------
int kpi_true(void) { return 1; }
int kpi_enodev(void) { return -19; }
long kpi_ida_alloc(void *ida, unsigned int min, unsigned int max, unsigned int gfp)
{
static long next;
(void)ida; (void)max; (void)gfp;
return (long)min + next++;
}
unsigned int
kpi_crc32_le(unsigned int crc, const unsigned char *p, unsigned long len)
{
while (len--) {
crc ^= *p++;
for (int k = 0; k < 8; k++)
crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1)));
}
return crc;
}
void
kpi_get_random_bytes(void *buf, unsigned long n)
{
static unsigned long long x = 0x9E3779B97F4A7C15ULL;
unsigned char *b = buf;
x ^= now_ns();
for (unsigned long i = 0; i < n; i++) {
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
b[i] = (unsigned char)x;
}
}
// __skb_pad(skb, pad, free_on_error): zero the bytes after the data, so a
// runt frame goes out padded with zeroes rather than with whatever was there.
int
kpi_skb_pad(void *skb, int pad, int free_on_error)
{
(void)free_on_error;
unsigned char *head = (unsigned char *)rdp(skb, kpi_l.skb_head);
unsigned int tail, end;
__builtin_memcpy(&tail, (char *)skb + kpi_l.skb_tail, 4);
__builtin_memcpy(&end, (char *)skb + kpi_l.skb_end, 4);
if (pad < 0 || tail + (unsigned)pad > end)
return -12;
for (int i = 0; i < pad; i++) head[tail + i] = 0;
return 0;
}
// --- data -------------------------------------------------------------------------------------
//
// Undefined data symbols. A stub would resolve them to machine code, which
// reads as nonsense and, written to, overwrites a trampoline.
// net/ethtool/common.c's link_mode_params: speed, lanes, duplex per link mode
// bit, which phylib reads at init to build its capability table. A fact table,
// generated from Linux 6.18; struct link_mode_info is int, u8, u8 (8 bytes).
static const struct { int speed; unsigned char lanes, duplex; } kpi_link_mode_params[] = {
#include "link_modes.inc"
};
static unsigned char kpi_zero_data[16][4096] __attribute__((aligned(64)));
static unsigned long kpi_cpu_online_mask[16] = { 1 }; // CPU 0
static int kpi_system_state = 3; // SYSTEM_RUNNING
// A list_head: empty means pointing at itself, not zero.
static void *kpi_leds_list[2] = { kpi_leds_list, kpi_leds_list };
// --- the table ------------------------------------------------------------------------------------
extern int kpi_queue_delayed_work_on(int cpu, void *q, void *dwork, unsigned long delay);
extern int kpi_cancel_work_sync(void *work);
extern void kpi_kfree_skb(void *skb);
const struct kpi_sym kpi_base_table[] = {
// printing, formatted
{ "_printk", (void *)kpi_printk_fmt },
{ "vprintk", (void *)kpi_vprintk },
{ "_dev_info", (void *)kpi_dev_printk },
{ "_dev_err", (void *)kpi_dev_printk },
{ "_dev_warn", (void *)kpi_dev_printk },
{ "_dev_notice", (void *)kpi_dev_printk },
{ "netdev_info", (void *)kpi_netdev_printk_fmt },
{ "netdev_err", (void *)kpi_netdev_printk_fmt },
{ "netdev_warn", (void *)kpi_netdev_printk_fmt },
{ "netdev_alert", (void *)kpi_netdev_printk_fmt },
{ "netdev_printk", (void *)kpi_netdev_printk_lvl },
{ "dev_err_probe", (void *)kpi_dev_err_probe },
{ "__warn_printk", (void *)kpi_warn_printk },
{ "snprintf", (void *)kpi_snprintf },
{ "sprintf", (void *)kpi_sprintf },
{ "kasprintf", (void *)kpi_kasprintf },
// strings
{ "strcmp", (void *)kpi_strcmp },
{ "strcpy", (void *)kpi_strcpy },
{ "kstrdup_const", (void *)kpi_kstrdup_const },
{ "kfree_const", (void *)kpi_nop },
{ "kstrtoull", (void *)kpi_kstrtoull },
// time
{ "ktime_get", (void *)kpi_ktime_get },
{ "__const_udelay", (void *)kpi_const_udelay },
{ "__udelay", (void *)kpi_udelay },
{ "__ndelay", (void *)kpi_ndelay },
{ "msleep", (void *)kpi_msleep },
{ "usleep_range_state", (void *)kpi_usleep_range },
// bitmaps
{ "__bitmap_and", (void *)kpi_bitmap_and },
{ "__bitmap_andnot", (void *)kpi_bitmap_andnot },
{ "__bitmap_or", (void *)kpi_bitmap_or },
{ "__bitmap_equal", (void *)kpi_bitmap_equal },
{ "__bitmap_intersects", (void *)kpi_bitmap_intersects },
{ "_find_first_bit", (void *)kpi_find_first_bit },
// the driver core
{ "driver_register", (void *)kpi_driver_register },
{ "driver_unregister", (void *)kpi_driver_unregister },
{ "device_add", (void *)kpi_device_add },
{ "device_register", (void *)kpi_device_add },
{ "device_del", (void *)kpi_device_del },
{ "device_unregister", (void *)kpi_device_del },
{ "device_initialize", (void *)kpi_nop },
{ "device_bind_driver", (void *)kpi_zero },
{ "device_release_driver", (void *)kpi_device_release_driver },
{ "get_device", (void *)kpi_get_device },
{ "put_device", (void *)kpi_nop },
{ "dev_set_name", (void *)kpi_dev_set_name },
{ "dev_driver_string", (void *)kpi_dev_driver_string },
{ "bus_register", (void *)kpi_zero },
{ "class_register", (void *)kpi_zero },
{ "try_module_get", (void *)kpi_true },
{ "module_put", (void *)kpi_nop },
// device-managed resources
{ "__devres_alloc_node", (void *)kpi_devres_alloc },
{ "devres_add", (void *)kpi_devres_add },
{ "devres_find", (void *)kpi_devres_find },
{ "devres_free", (void *)kpi_devres_free },
{ "devm_kmalloc", (void *)kpi_devm_kmalloc },
{ "devm_alloc_etherdev_mqs", (void *)kpi_devm_alloc_etherdev },
{ "dmam_alloc_attrs", (void *)kpi_dmam_alloc_attrs },
// PCI
{ "pcim_enable_device", (void *)kpi_pcim_enable_device },
{ "pcim_iomap_region", (void *)kpi_pcim_iomap_region },
{ "pci_alloc_irq_vectors", (void *)kpi_pci_alloc_irq_vectors },
{ "pci_irq_vector", (void *)kpi_pci_irq_vector },
{ "pci_read_config_byte", (void *)kpi_pci_read_config_byte },
{ "pci_write_config_byte", (void *)kpi_pci_write_config_byte },
{ "__alloc_pages_noprof", (void *)kpi_alloc_pages_node },
{ "__free_pages", (void *)kpi_nop },
// work
{ "mod_delayed_work_on", (void *)kpi_mod_delayed_work_on },
{ "alloc_workqueue_noprof", (void *)kpi_alloc_workqueue },
{ "destroy_workqueue", (void *)kpi_nop },
{ "disable_work_sync", (void *)kpi_disable_work_sync },
{ "disable_work", (void *)kpi_disable_work_sync },
{ "enable_work", (void *)kpi_true },
{ "system_power_efficient_wq", (void *)&kpi_system_wq },
{ "system_percpu_wq", (void *)&kpi_system_wq },
{ "system_wq", (void *)&kpi_system_wq },
// firmware
{ "firmware_request_nowarn", (void *)kpi_request_firmware },
{ "request_firmware", (void *)kpi_request_firmware },
{ "release_firmware", (void *)kpi_nop },
// must not be zero
{ "__list_add_valid_or_report", (void *)kpi_true },
{ "__list_del_entry_valid_or_report", (void *)kpi_true },
{ "mutex_is_locked", (void *)kpi_true },
{ "eth_platform_get_mac_address", (void *)kpi_enodev },
{ "ida_alloc_range", (void *)kpi_ida_alloc },
{ "crc32_le", (void *)kpi_crc32_le },
{ "get_random_bytes", (void *)kpi_get_random_bytes },
{ "__skb_pad", (void *)kpi_skb_pad },
// r8169 frees every transmitted skb this way; a stub leaked one per frame
// until the DMA heap ran out. The budget argument changes only which cache
// Linux returns it to.
{ "napi_consume_skb", (void *)kpi_kfree_skb },
// data
{ "link_mode_params", (void *)kpi_link_mode_params },
{ "__cpu_online_mask", (void *)kpi_cpu_online_mask },
{ "system_state", (void *)&kpi_system_state },
{ "phylib_stubs", (void *)kpi_zero_data[0] },
{ "phys_base", (void *)kpi_zero_data[7] },
{ "kmalloc_caches", (void *)kpi_zero_data[1] },
{ "random_kmalloc_seed", (void *)kpi_zero_data[2] },
{ "mem_alloc_profiling_key", (void *)kpi_zero_data[3] },
{ "of_fwnode_ops", (void *)kpi_zero_data[4] },
{ "leds_list", (void *)kpi_leds_list },
{ "leds_list_lock", (void *)kpi_zero_data[6] },
};
const int kpi_base_count = (int)(sizeof(kpi_base_table) / sizeof(kpi_base_table[0]));