L4 was listed as "free -- H2 built all of this" and never actually tested,
which is a claim rather than a result. Tested now, and it holds:
ldlsd running ping 2/2; holds index 7 and DMA slots 3 and 4
killed interface DOWN, device released, bus mastering
cleared, DMA reclaimed -- back to vgpud's 3 blocks
ping fails honestly: sendto: -5
a second ldlsd ping 3/3, and the arena hands back the same addresses
pciuser_release_all() and netuser_release_all() sit beside each other in
exit() for the same reason, and that they apply unchanged to a foreign driver
is the whole point of having built them against our own.
With that, every milestone on ldls.md is done, so it leaves docs/plan/ for
docs/architecture/ -- the rule docs/plan/README.md states and the one that
keeps the two directories meaning different things. The new document describes
what is, not what was planned: the five rules, the four pieces, what it costs
(polling, no stack protector, locks that are no-ops), that it can be killed,
and a "what it does not do" that leads with one driver, one kernel version,
tried once.
Sixteen source files pointed at docs/plan/ldls.md and would have been dangling
the moment it moved. All repointed, and the milestone references -- which the
architecture doc deliberately does not have -- rewritten to name sections
instead, so a reader lands somewhere real.
The register row closes with it. hybrid.md freed a slot, ldls.md took it, and
it is now free again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
99 lines
3.9 KiB
C
99 lines
3.9 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// The loader's test object. **Ours, on purpose.**
|
|
//
|
|
// docs/architecture/ldls.md rule 1 says nothing GPL ever enters an image we build, and
|
|
// the same rule applies to the tree itself: there is no `.ko` committed here
|
|
// and there never will be. So the thing `make ldls-check` loads is this file,
|
|
// compiled with `-c`, which produces exactly what a `.ko` is -- an ELF64
|
|
// relocatable object for x86-64.
|
|
//
|
|
// It is written to contain one of each thing the loader has to get right,
|
|
// because a test object that only exercises the easy path is a test that
|
|
// passes on a loader that does not work:
|
|
//
|
|
// - undefined symbols, of both the function and the data kind
|
|
// - a `.text` with calls needing PC32/PLT32
|
|
// - `.data` with an absolute pointer needing R_X86_64_64
|
|
// - `.rodata` a pointer points into, so section-relative values matter
|
|
// - `.bss`, which must arrive zeroed and is NOBITS with no file content
|
|
// - a `.modinfo` shaped the way a module's is, so ko_modinfo() is exercised
|
|
//
|
|
// It is never linked and never run. Compiling it is the whole of its job.
|
|
|
|
// Things this object does not define. A real module's undefined list is
|
|
// hundreds of names from the kernel it was built against; five is enough to
|
|
// prove the loader reports them rather than to pretend this is a driver.
|
|
extern int ldls_test_alloc(unsigned long size);
|
|
extern void ldls_test_free(void *p);
|
|
extern int ldls_test_print(const char *s);
|
|
extern int ldls_test_counter; // undefined *data*, not a function
|
|
|
|
// .rodata, and something in .data pointing at it -- which is the case that
|
|
// needs a section-relative symbol value to come out right.
|
|
static const char greeting[] = "ldls reference module";
|
|
const char *ldls_test_greeting = greeting;
|
|
|
|
// .bss. The loader must zero this rather than copy it, because NOBITS has no
|
|
// bytes in the file to copy.
|
|
static char scratch[256];
|
|
char *ldls_test_scratch = scratch;
|
|
|
|
// .data with a known value, so a test can check the bytes survived layout.
|
|
int ldls_test_magic = 0x1D15;
|
|
|
|
// .text with calls, which is where PC32 and PLT32 come from.
|
|
int
|
|
ldls_test_entry(void)
|
|
{
|
|
int n = ldls_test_alloc(sizeof(scratch));
|
|
|
|
if (n == 0)
|
|
return -1;
|
|
|
|
ldls_test_print(greeting);
|
|
ldls_test_counter++;
|
|
scratch[0] = (char)ldls_test_magic;
|
|
ldls_test_free(scratch);
|
|
return ldls_test_counter;
|
|
}
|
|
|
|
// A second function, called by the first, so at least one relocation resolves
|
|
// inside the object and does not need the resolver at all.
|
|
int
|
|
ldls_test_twice(void)
|
|
{
|
|
return ldls_test_entry() + ldls_test_entry();
|
|
}
|
|
|
|
// Shaped like a module's `.modinfo`: NUL-terminated key=value strings in their
|
|
// own section. A real one is produced by MODULE_LICENSE() and friends; this is
|
|
// the same bytes by hand, because the macros that emit them are not ours.
|
|
#define MODINFO __attribute__((section(".modinfo"), used, aligned(1)))
|
|
MODINFO static const char mi_license[] = "license=Apache-2.0";
|
|
MODINFO static const char mi_description[] = "description=LDLS loader test object";
|
|
MODINFO static const char mi_name[] = "name=refmod";
|
|
|
|
// Modules define this; ko_name() looks for it to tell a kernel module from an
|
|
// ordinary object file.
|
|
int __this_module;
|
|
|
|
// The entry point, under the name `module_init(x)` gives it.
|
|
//
|
|
// L1 calls this, so it is written to exercise the thing L1 is for: a sequence
|
|
// of calls out to symbols this object does not define, in an order that can be
|
|
// checked. Three calls, always the same three, always in this order -- which
|
|
// is exactly what a real driver's init produces and what no amount of reading
|
|
// the symbol table would tell you.
|
|
//
|
|
// It deliberately does *not* dereference what the stubs return. A real driver
|
|
// does, faults, and that is a useful result; a test that faults every time is
|
|
// a test that cannot tell a fault it expected from one it did not.
|
|
int
|
|
init_module(void)
|
|
{
|
|
ldls_test_print(greeting);
|
|
ldls_test_alloc(sizeof(scratch));
|
|
ldls_test_free(scratch);
|
|
return 0;
|
|
}
|