On the TUF A15 Linux's r8169 found its PHY and read its ID as 0x02460246. mdio-bus keeps the register it has just read in r11 across preempt_enable(), which on x86 is `decl %gs:__preempt_count; je` to a call that preserves every register. LDLS gave __preempt_count zeroes, so the count reached zero and the call was made -- to a logging stub, whose syscall left RFLAGS (0x246) in r11. __preempt_count now starts at PREEMPT_ENABLED (x86 keeps NEED_RESCHED inverted), and preempt_schedule and its notrace twin are a bare ret. The same laptop reported its battery full and on AC, unplugged. The EC owns both, and with no EmbeddedControl handler _REG never told the firmware the EC was usable, so _BST and _PSR answered their fallbacks. The EC is found by PNP0C09 or the ECDT and served by the ACPI 12.3 byte protocol, installed before _INI. SCI queries are not handled yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
985 lines
32 KiB
C
985 lines
32 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 };
|
|
|
|
// --- calls that must not touch a register ---------------------------------------------
|
|
//
|
|
// preempt_schedule is reached through a thunk that saves every register, so
|
|
// its callers keep live values in caller-saved ones across the call. A
|
|
// logging stub makes a syscall, and `syscall` writes RFLAGS into r11: mdio-
|
|
// bus kept the PHY register it had just read in r11, and every MDIO read on
|
|
// the RTL8168H came back 0x0246 -- the PHY ID read as 0x02460246 and no
|
|
// driver matched it. Nothing to schedule here anyway; a bare ret.
|
|
__asm__(".text\n.globl kpi_ret\nkpi_ret:\n\tret\n");
|
|
extern void kpi_ret(void);
|
|
|
|
// --- 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 preserve every register
|
|
{ "__SCT__preempt_schedule", (void *)kpi_ret },
|
|
{ "__SCT__preempt_schedule_notrace", (void *)kpi_ret },
|
|
{ "preempt_schedule_thunk", (void *)kpi_ret },
|
|
{ "preempt_schedule_notrace_thunk", (void *)kpi_ret },
|
|
|
|
// 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]));
|