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
58 lines
2.6 KiB
C
58 lines
2.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// Stubs: giving every undefined symbol an address that says who it was.
|
|
//
|
|
// docs/architecture/ldls.md, rule 5. The loader measures what a module asks for; this is
|
|
// what answers. Every name resolves to a small block of generated code, and
|
|
// when the driver calls it the block says which name it was and what arguments
|
|
// it came with.
|
|
//
|
|
// The value is not the logging, it is the **order**. A module's undefined
|
|
// symbol list is a set and tells you nothing about sequence; what a driver
|
|
// does at init, and in what order, is the other thing that cannot be guessed
|
|
// and it falls out of running the thing.
|
|
//
|
|
// Freestanding, like ldls/ko.c, so the SchOS-side host gets the same code.
|
|
|
|
#define STUB_SIZE 32 // bytes per trampoline, generously aligned
|
|
|
|
// What a stub call reports. The embedder provides this; stub.c calls it from
|
|
// the thunk with the original argument registers intact, which is what lets a
|
|
// stub for `_printk` show the string the driver actually passed.
|
|
//
|
|
// Whatever it returns becomes the stub's return value, so returning 0 lets a
|
|
// driver carry on past a call and reveal the next one -- which is worth far
|
|
// more than stopping at the first.
|
|
unsigned long ldls_stub(long idx, unsigned long *args);
|
|
|
|
struct stubs {
|
|
unsigned char *code; // STUB_SIZE * n bytes, must be executable
|
|
int n;
|
|
};
|
|
|
|
// Build `n` trampolines into `code`. Each one, when called, ends up in
|
|
// ldls_stub() with its index.
|
|
//
|
|
// `code` has to be executable memory; this only writes the bytes.
|
|
void stub_build(struct stubs *s, unsigned char *code, int n);
|
|
// The same, numbering the stubs from `first`: with several modules loaded,
|
|
// each module's stubs follow the previous module's in one shared name table.
|
|
void stub_build_from(struct stubs *s, unsigned char *code, int n, int first);
|
|
|
|
// The address of stub `i`, to hand to the loader as a symbol value.
|
|
unsigned long long stub_addr(const struct stubs *s, int i);
|
|
|
|
// Some names must not be logged, because they are called so often that the log
|
|
// becomes the only thing you see, and because they have calling conventions of
|
|
// their own that a logging thunk would break.
|
|
//
|
|
// `__fentry__` is ftrace's hook, emitted at the top of every function, and it
|
|
// is required to preserve every register. `__x86_return_thunk` is the retpoline
|
|
// return. Both are correctly implemented as a bare `ret`, which is also the
|
|
// fastest thing they could possibly be.
|
|
//
|
|
// Returns 1 if the name got a bare-ret stub instead of a logging one.
|
|
int stub_is_bare(const char *name);
|
|
|
|
// Overwrite one stub with that bare `ret`.
|
|
void stub_make_bare(unsigned char *p);
|