Linux's own r8169 through LDLS, deep idle, and the GPU plan
The RTL8168 is now driven by Linux's r8169, unmodified, in a process. `make ldls-r8169` stages it from the running kernel together with libphy, mdio-bus, mdio_devres and the Realtek PHY driver, the kernel's BTF and the RTL PHY firmware, and etc/schinit/ldls-r8169.job starts it at boot with its log in /var/log/ldls-r8169.log. The native driver is out of the registry; its code, now with Linux's 8168H setup, stays as the fallback. LDLS grows what that needed: - ldlsd loads a comma-separated module set in dependency order, each linking against the ones before it, with BTF read before any init_module runs; - a driver core (bus match, driver probe), devres, kernel printf, real udelay/msleep/ktime and 1000 Hz jiffies, phylib's bitmaps and link_mode_params, firmware from /lib/firmware, and the symbols whose stub zero was a lie (list validity, try_module_get, dev_err_probe, eth_platform_get_mac_address); - pdev->bus is a real pci_bus, and cmovcc from per-CPU memory is rewritten like the other %gs forms. In QEMU all five modules load, link and initialise, and r8169 registers and finds no device. Probe and traffic are untested until real hardware. The kernel turns ASPM off on Realtek NIC links at boot, as r8169 does through pci_disable_link_state, which a process cannot. Idle CPUs enter ACPI _CST's C2 (CC6 on Zen) by port read or MWAIT when the APIC timer keeps running, instead of only ever halting at C1. docs/plan/gpu.md: amdgpu and nouveau through LDLS, with measurements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
438e7af141
commit
4ad49ed5f2
18 changed files with 1945 additions and 81 deletions
326
cmd/ldlsd/main.c
326
cmd/ldlsd/main.c
|
|
@ -185,6 +185,32 @@ ldls_stub(long idx, unsigned long *args)
|
|||
|
||||
struct rctx { struct ko *k; struct stubs *s; };
|
||||
|
||||
// Modules loaded before the one being relocated. A module set is loaded in
|
||||
// dependency order -- libphy before r8169, ttm before amdgpu -- and what an
|
||||
// earlier one defines is what a later one imports from it.
|
||||
#define MAXMOD 32
|
||||
static struct ko *g_mods[MAXMOD];
|
||||
static int g_nmods;
|
||||
|
||||
static unsigned long long
|
||||
find_global(struct ko *k, const char *want)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < k->nsym; i++) {
|
||||
if ((k->sym[i].st_info >> 4) == 0) // STB_LOCAL: not for others
|
||||
continue;
|
||||
if (k->sym[i].st_shndx == KO_SHN_UNDEF ||
|
||||
k->sym[i].st_shndx >= (unsigned short)k->nsh)
|
||||
continue;
|
||||
if (!k->sec[k->sym[i].st_shndx].alloc)
|
||||
continue;
|
||||
if (!streq(k->str + k->sym[i].st_name, want))
|
||||
continue;
|
||||
return k->sec[k->sym[i].st_shndx].addr + k->sym[i].st_value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The KPI first, a logging stub second.
|
||||
//
|
||||
// An unimplemented symbol still resolves -- to something that reports itself
|
||||
|
|
@ -208,6 +234,14 @@ resolve(void *ctx, const char *name, unsigned long long *out)
|
|||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < g_nmods; i++) {
|
||||
unsigned long long a = find_global(g_mods[i], name);
|
||||
if (a) {
|
||||
*out = a;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < r->k->nundef; i++)
|
||||
if (streq(r->k->undef[i], name)) {
|
||||
*out = stub_addr(r->s, i);
|
||||
|
|
@ -445,8 +479,42 @@ kpi_layout_from_btf(struct btf *b)
|
|||
GET(pd, "dev", kpi_l.pd_dev);
|
||||
GET(dv, "kobj", kpi_l.dev_kobj);
|
||||
GET(ko, "name", kpi_l.kobj_name);
|
||||
kpi_l.dev_kobj_ok = 1;
|
||||
}
|
||||
}
|
||||
// The driver core and struct firmware. Optional: a driver that never
|
||||
// registers a bus or loads firmware does not need them, so a BTF without
|
||||
// one of these turns the feature off rather than refusing to run.
|
||||
{
|
||||
int dv = btf_find(b, "device", BTF_KIND_STRUCT);
|
||||
int dd = btf_find(b, "device_driver", BTF_KIND_STRUCT);
|
||||
int bt = btf_find(b, "bus_type", BTF_KIND_STRUCT);
|
||||
int fw = btf_find(b, "firmware", BTF_KIND_STRUCT);
|
||||
int ok = dv && dd && bt;
|
||||
#define OPT(id, name, field) \
|
||||
do { if (btf_member_off(b, id, name, &off, &size) < 0) ok = 0; \
|
||||
else field = off; } while (0)
|
||||
if (ok) {
|
||||
OPT(dv, "bus", kpi_l.dev_bus);
|
||||
OPT(dv, "driver", kpi_l.dev_driver);
|
||||
OPT(dd, "bus", kpi_l.drv_bus);
|
||||
OPT(dd, "probe", kpi_l.drv_probe);
|
||||
OPT(dd, "name", kpi_l.drv_name);
|
||||
OPT(bt, "match", kpi_l.bus_match);
|
||||
OPT(bt, "probe", kpi_l.bus_probe);
|
||||
}
|
||||
kpi_l.core_ok = ok;
|
||||
if (fw) {
|
||||
ok = 1;
|
||||
OPT(fw, "size", kpi_l.fw_size);
|
||||
OPT(fw, "data", kpi_l.fw_data);
|
||||
kpi_l.fw_size_of = btf_type_size(b, fw);
|
||||
kpi_l.fw_ok = ok;
|
||||
}
|
||||
#undef OPT
|
||||
puts_(" kpi driver core "); puts_(kpi_l.core_ok ? "on" : "off");
|
||||
puts_(", firmware "); puts_(kpi_l.fw_ok ? "on" : "off"); puts_("\n");
|
||||
}
|
||||
{
|
||||
int nq = btf_find(b, "netdev_queue", BTF_KIND_STRUCT);
|
||||
if (!nq) { puts_("ldlsd: no struct netdev_queue\n"); return -1; }
|
||||
|
|
@ -682,8 +750,11 @@ drive_probe(const char *btfpath)
|
|||
{
|
||||
struct pciuser_info in;
|
||||
|
||||
// No device: not a failure worth restarting for. schinit's
|
||||
// KeepAlive would otherwise start this again every few seconds on
|
||||
// every machine without the NIC. See SCHINIT_EXIT_NORESTART.
|
||||
if (claim_for(idtab, id_size, id_vendor, id_device, &in) < 0)
|
||||
return;
|
||||
exit_group(78);
|
||||
|
||||
pdev = (unsigned long)getmem(dev_size);
|
||||
if (!pdev) { puts_("ldlsd: no memory for a pci_dev\n"); return; }
|
||||
|
|
@ -714,6 +785,26 @@ drive_probe(const char *btfpath)
|
|||
(unsigned char)(q >> (i * 8));
|
||||
}
|
||||
|
||||
// pdev->bus. pci_domain_nr() and pci_dev_id() are inlines that
|
||||
// dereference it -- r8169 names its MDIO bus with both -- and a null
|
||||
// one is a fault at the first line of MDIO setup.
|
||||
{
|
||||
unsigned int pd_bus = lay(&b, "pci_dev", "bus");
|
||||
unsigned int pb_num = lay(&b, "pci_bus", "number");
|
||||
unsigned int pb_sys = lay(&b, "pci_bus", "sysdata");
|
||||
unsigned int pb_size = lay(&b, "pci_bus", 0);
|
||||
if (pd_bus != 0xffffffffu && pb_num != 0xffffffffu &&
|
||||
pb_sys != 0xffffffffu && pb_size != 0xffffffffu) {
|
||||
unsigned long bus = (unsigned long)getmem(pb_size);
|
||||
unsigned long sys = (unsigned long)getmem(4096);
|
||||
if (bus && sys) {
|
||||
((unsigned char *)bus)[pb_num] = (unsigned char)in.bus;
|
||||
bcopy_((void *)(bus + pb_sys), &sys, 8);
|
||||
bcopy_((void *)(pdev + pd_bus), &bus, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wr16(pdev, dev_vendor, in.vendor);
|
||||
wr16(pdev, dev_device, in.device);
|
||||
wr32(pdev, dev_devfn, (unsigned int)((in.slot << 3) | in.func));
|
||||
|
|
@ -795,21 +886,139 @@ drive_probe(const char *btfpath)
|
|||
}
|
||||
}
|
||||
|
||||
// --- main ----------------------------------------------------------------------
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
// Read the BTF and fill in the KPI's layouts, before any module runs.
|
||||
static void
|
||||
early_layout(const char *btfpath)
|
||||
{
|
||||
struct ko k;
|
||||
static struct btf b;
|
||||
unsigned long long blen;
|
||||
unsigned char *bimg = slurp(btfpath, &blen);
|
||||
long nt;
|
||||
unsigned int *boff;
|
||||
|
||||
if (!bimg) return;
|
||||
nt = btf_count(bimg, blen);
|
||||
if (nt < 0) return;
|
||||
boff = getmem(((unsigned long long)nt + 2) * 4);
|
||||
if (!boff || btf_open(&b, bimg, blen, boff, (unsigned int)nt + 2) < 0)
|
||||
return;
|
||||
{
|
||||
extern unsigned int kpi_netdev_size;
|
||||
int nd = btf_find(&b, "net_device", BTF_KIND_STRUCT);
|
||||
kpi_netdev_size = nd ? btf_type_size(&b, nd) : 0;
|
||||
}
|
||||
kpi_layout_from_btf(&b);
|
||||
}
|
||||
|
||||
// One module: read, laid out, relocated, its %gs references fixed, and its
|
||||
// init_module called. Kept loaded for the ones after it to link against.
|
||||
static void
|
||||
load_module(const char *path)
|
||||
{
|
||||
struct ko *k;
|
||||
struct ko_section *sec;
|
||||
struct stubs stubs;
|
||||
struct stubs *stubs;
|
||||
struct rctx rc;
|
||||
unsigned char *img, *mem, *code;
|
||||
unsigned long long len, span, entry;
|
||||
int nsec, i;
|
||||
|
||||
k = getmem(sizeof(*k));
|
||||
stubs = getmem(sizeof(*stubs));
|
||||
if (!k || !stubs) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
|
||||
img = slurp(path, &len);
|
||||
if (!img) { puts_("ldlsd: cannot read "); puts_(path); puts_("\n"); exit(); }
|
||||
|
||||
nsec = ko_sections_needed(img, len);
|
||||
if (nsec < 0) {
|
||||
puts_("ldlsd: that is not an ELF64 relocatable object\n");
|
||||
exit();
|
||||
}
|
||||
sec = getmem((unsigned long long)(nsec + 1) * sizeof(struct ko_section));
|
||||
if (!sec) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
|
||||
if (ko_open(k, img, len, sec, nsec) < 0) {
|
||||
puts_("ldlsd: "); puts_(ko_err(k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
puts_(path); puts_("\n");
|
||||
puts_(" module ");
|
||||
puts_(ko_name(k) ? ko_name(k) : "(not a kernel module)"); puts_("\n");
|
||||
puts_(" license ");
|
||||
puts_(ko_modinfo(k, "license") ? ko_modinfo(k, "license")
|
||||
: "(none declared)"); puts_("\n");
|
||||
puts_(" wants "); putn(k->nundef); puts_(" symbols\n");
|
||||
|
||||
span = ko_span(k);
|
||||
mem = getmem(span ? span : 4096);
|
||||
code = getmem((unsigned long long)(k->nundef + 1) * STUB_SIZE);
|
||||
if (!mem || !code) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
|
||||
stub_build_from(stubs, code, k->nundef, g_count);
|
||||
for (i = 0; i < k->nundef; i++) {
|
||||
g_names[g_count + i] = k->undef[i];
|
||||
if (stub_is_bare(k->undef[i]))
|
||||
stub_make_bare(code + (unsigned long long)i * STUB_SIZE);
|
||||
}
|
||||
g_count += k->nundef;
|
||||
k_nundef_global = g_count;
|
||||
|
||||
if (ko_layout(k, mem, span, (unsigned long long)mem) < 0) {
|
||||
puts_("ldlsd: layout: "); puts_(ko_err(k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
rc.k = k; rc.s = stubs;
|
||||
if (ko_relocate(k, resolve, &rc) < 0) {
|
||||
puts_("ldlsd: relocate: "); puts_(ko_err(k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
// The stack canary is fetched through %gs, which belongs to the kernel
|
||||
// here. See ko_defuse_gs().
|
||||
{
|
||||
int g = ko_defuse_gs(k, "__ref_stack_chk_guard");
|
||||
if (g < 0) g = 0;
|
||||
if (g == 0) g = ko_defuse_gs(k, "__stack_chk_guard");
|
||||
if (g > 0) { puts_(" defused "); putn(g); puts_(" %gs stack-guard load(s)\n"); }
|
||||
}
|
||||
// Whatever %gs references are left name real per-CPU variables, and those
|
||||
// cannot be defused -- the driver dereferences what it reads.
|
||||
{
|
||||
int p = ko_fixup_percpu(k, percpu_for, 0);
|
||||
if (p < 0) {
|
||||
puts_("ldlsd: per-CPU: "); puts_(ko_err(k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
if (p > 0) { puts_(" rehomed "); putn(p); puts_(" %gs per-CPU reference(s)\n"); }
|
||||
}
|
||||
|
||||
puts_(" loaded "); putn(span); puts_(" bytes at "); puthex((unsigned long long)mem);
|
||||
puts_("\n");
|
||||
|
||||
entry = find_symbol(k, "init_module");
|
||||
if (g_nmods < MAXMOD)
|
||||
g_mods[g_nmods++] = k;
|
||||
if (!entry) { puts_(" no init_module -- nothing to call\n"); return; }
|
||||
|
||||
puts_("\n calling init_module\n\n");
|
||||
{
|
||||
int (*init)(void) = (int (*)(void))(void *)entry;
|
||||
int r = init();
|
||||
puts_("\n init_module returned "); putn(r);
|
||||
puts_(" after "); putn(g_calls); puts_(" stub call(s)\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --- main ----------------------------------------------------------------------
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
if (argc < 2) {
|
||||
puts_("usage: ldlsd <module.ko> [vmlinux-btf] [bus:slot.func]\n");
|
||||
puts_("usage: ldlsd <module.ko>[,<module.ko>...] [vmlinux-btf] [bus:slot.func]\n");
|
||||
puts_(" neither file ships with SchOS; both are yours.\n");
|
||||
exit();
|
||||
}
|
||||
|
|
@ -837,85 +1046,32 @@ main(int argc, char *argv[])
|
|||
puthexn(sel_slot, 2); puts_("."); puthexn(sel_func, 1); puts_("\n");
|
||||
}
|
||||
|
||||
img = slurp(argv[1], &len);
|
||||
if (!img) { puts_("ldlsd: cannot read "); puts_(argv[1]); puts_("\n"); exit(); }
|
||||
|
||||
nsec = ko_sections_needed(img, len);
|
||||
if (nsec < 0) {
|
||||
puts_("ldlsd: that is not an ELF64 relocatable object\n");
|
||||
exit();
|
||||
}
|
||||
sec = getmem((unsigned long long)(nsec + 1) * sizeof(struct ko_section));
|
||||
if (!sec) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
|
||||
if (ko_open(&k, img, len, sec, nsec) < 0) {
|
||||
puts_("ldlsd: "); puts_(ko_err(&k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
puts_(argv[1]); puts_("\n");
|
||||
puts_(" module ");
|
||||
puts_(ko_name(&k) ? ko_name(&k) : "(not a kernel module)"); puts_("\n");
|
||||
puts_(" license ");
|
||||
puts_(ko_modinfo(&k, "license") ? ko_modinfo(&k, "license")
|
||||
: "(none declared)"); puts_("\n");
|
||||
puts_(" wants "); putn(k.nundef); puts_(" symbols\n");
|
||||
|
||||
span = ko_span(&k);
|
||||
mem = getmem(span ? span : 4096);
|
||||
code = getmem((unsigned long long)(k.nundef + 1) * STUB_SIZE);
|
||||
if (!mem || !code) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
|
||||
stub_build(&stubs, code, k.nundef);
|
||||
g_names = k.undef;
|
||||
g_count = k.nundef;
|
||||
k_nundef_global = k.nundef;
|
||||
kpi.netfd = -1;
|
||||
for (i = 0; i < k.nundef; i++)
|
||||
if (stub_is_bare(k.undef[i]))
|
||||
stub_make_bare(code + (unsigned long long)i * STUB_SIZE);
|
||||
|
||||
if (ko_layout(&k, mem, span, (unsigned long long)mem) < 0) {
|
||||
puts_("ldlsd: layout: "); puts_(ko_err(&k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
rc.k = &k; rc.s = &stubs;
|
||||
if (ko_relocate(&k, resolve, &rc) < 0) {
|
||||
puts_("ldlsd: relocate: "); puts_(ko_err(&k)); puts_("\n");
|
||||
exit();
|
||||
}
|
||||
// The stack canary is fetched through %gs, which belongs to the kernel
|
||||
// here. See ko_defuse_gs().
|
||||
// argv[1] is one module, or a set in dependency order separated by
|
||||
// commas: libphy.ko,realtek.ko,r8169.ko. Each is loaded, relocated
|
||||
// against the KPI and the ones before it, and initialised before the next.
|
||||
{
|
||||
int g = ko_defuse_gs(&k, "__ref_stack_chk_guard");
|
||||
if (g < 0) g = 0;
|
||||
if (g == 0) g = ko_defuse_gs(&k, "__stack_chk_guard");
|
||||
if (g > 0) { puts_(" defused "); putn(g); puts_(" %gs stack-guard load(s)\n"); }
|
||||
}
|
||||
// Whatever %gs references are left name real per-CPU variables, and those
|
||||
// cannot be defused -- the driver dereferences what it reads.
|
||||
{
|
||||
int p = ko_fixup_percpu(&k, percpu_for, 0);
|
||||
if (p < 0) {
|
||||
puts_("ldlsd: per-CPU: "); puts_(ko_err(&k)); puts_("\n");
|
||||
exit();
|
||||
static char list[4096];
|
||||
char *p = list, *name;
|
||||
int n = 0;
|
||||
while (argv[1][n] && n < (int)sizeof(list) - 1) { list[n] = argv[1][n]; n++; }
|
||||
list[n] = 0;
|
||||
// The layouts first: init_module is where a module registers its
|
||||
// drivers, and the driver core needs struct offsets to do that.
|
||||
if (argc > 2)
|
||||
early_layout(argv[2]);
|
||||
g_names = getmem(sizeof(char *) * (unsigned long long)MAXMOD * KO_MAXUNDEF);
|
||||
if (!g_names) { puts_("ldlsd: out of memory\n"); exit(); }
|
||||
kpi.netfd = -1;
|
||||
for (;;) {
|
||||
name = p;
|
||||
while (*p && *p != ',') p++;
|
||||
int last = *p == 0;
|
||||
*p = 0;
|
||||
if (*name)
|
||||
load_module(name);
|
||||
if (last) break;
|
||||
p++;
|
||||
}
|
||||
if (p > 0) { puts_(" rehomed "); putn(p); puts_(" %gs per-CPU reference(s)\n"); }
|
||||
}
|
||||
|
||||
puts_(" loaded "); putn(span); puts_(" bytes at "); puthex((unsigned long long)mem);
|
||||
puts_("\n");
|
||||
|
||||
entry = find_symbol(&k, "init_module");
|
||||
if (!entry) { puts_(" no init_module -- nothing to call\n"); exit(); }
|
||||
|
||||
puts_("\n calling init_module\n\n");
|
||||
{
|
||||
int (*init)(void) = (int (*)(void))(void *)entry;
|
||||
int r = init();
|
||||
puts_("\n init_module returned "); putn(r);
|
||||
puts_(" after "); putn(g_calls); puts_(" stub call(s)\n");
|
||||
}
|
||||
|
||||
if (argc > 2 && g_pci_driver)
|
||||
|
|
|
|||
Loading…
Reference in a new issue