ldls: being the PCI core, with offsets nobody wrote down

docs/plan/ldls.md milestone L2, less the half that needs /dev/pci.

In Linux, __pci_register_driver ends at the PCI core, which matches a
driver's id_table against the bus and calls probe(). There is no PCI core
here, so LDLS keeps the pointer that call was given and plays the part.

To do that it must know where pci_driver.probe is, and it asks the kernel
rather than being told by us. ldls/btf.c reads /sys/kernel/btf/vmlinux --
122,279 types, every struct layout and every function prototype, as generated
data describing the kernel that is installed. Implemented from
Documentation/bpf/btf.rst, which is a published specification.

    $ ldls-types /sys/kernel/btf/vmlinux --need
      pci_driver   probe      offset    16  size 8
      pci_dev      resource   offset  1000  size 1088
      resource     start      offset     0  size 8
      all present

Cross-checked against pahole on the same file, field for field. No offset in
this is ours, which is rule 3's uncomfortable row disappearing rather than
being argued about.

And then the driver:

      driver name "8139too"
      devices it claims: 10ec:8139 ... and 16 more
      calling probe(pci_dev 10ec:8139, ...)
        1  alloc_etherdev_mqs(0x368, 1, 1)
        2  pci_enable_device / 3 pci_request_regions / 4 pci_set_master
        5  pci_iomap(pdev, 1, 0)
        6..544  ioread8 / iowrite8 -- 0x88, 0x8a, 0x8c, 0x8e, over and over
      545  dev_addr_mod / 547 netif_napi_add_weight_locked
      549  timer_init_key / 550 register_netdev / 551 netdev_info
      probe returned 0 after 552 stub call(s)

10ec:8139 is the real RTL8139 id. It mapped BAR 1, the one marked
IORESOURCE_MEM. Those 538 alternating byte accesses stepping 0x88/0x8a/0x8c/
0x8e are serial EEPROM clocking -- the driver bit-banging the chip for its MAC
address and handing the result to dev_addr_mod. Then NAPI, a watchdog timer,
register_netdev, its "found it" line, and zero.

Fifteen distinct symbols carry that entire probe, and three of them are 540 of
the 552 calls. L1 said 44 symbols carry 110 modules through init; L2 says 15
carry this one through probe. Neither is a number anyone would have guessed
and both came from running the thing.

Recorded as not done, because it is not: probe receives a pci_dev whose shape
is right and whose contents are invented. The BARs are made up, pci_iomap
returns a zeroed page, and 8139too read a MAC address of zeroes and was
perfectly happy about it. The "all through /dev/pci" half of L2 belongs to the
SchOS side. Also recorded: BTF has to come from somewhere, and SchOS has no
/sys/kernel/btf/vmlinux -- the user brings it with the module, from the kernel
the module was built for.

make ldls-check is 27 checks, four of them L2's, and it says so out loud when
it skips them on a machine with no BTF rather than passing silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-14 08:14:57 +02:00
commit 2d9a0ec6e3
8 changed files with 978 additions and 9 deletions

View file

@ -926,6 +926,7 @@ crashqr:
# -----------------------------------------------------------------------
LDLS_INSPECT = $(BUILD)/tools/ldls-inspect$(EXE)
LDLS_RUN = $(BUILD)/tools/ldls-run$(EXE)
LDLS_TYPES = $(BUILD)/tools/ldls-types$(EXE)
LDLS_REFMOD = $(BUILD)/ldls/refmod.o
$(LDLS_INSPECT): ldls/host/inspect.c ldls/ko.c ldls/ko.h
@ -935,10 +936,11 @@ $(LDLS_INSPECT): ldls/host/inspect.c ldls/ko.c ldls/ko.h
# L1. Calls a module's init with every symbol stubbed, to read off the call
# order. -fno-strict-aliasing because this deliberately treats loaded bytes as
# code, and no optimiser assumption about that is one we want.
$(LDLS_RUN): ldls/host/run.c ldls/ko.c ldls/stub.c ldls/ko.h ldls/stub.h
$(LDLS_RUN): ldls/host/run.c ldls/ko.c ldls/stub.c ldls/btf.c \
ldls/ko.h ldls/stub.h ldls/btf.h
@mkdir -p $(BUILD)/tools
$(HOSTCC) $(HOSTWARN) -O1 -fno-strict-aliasing -o $@ \
ldls/host/run.c ldls/ko.c ldls/stub.c
ldls/host/run.c ldls/ko.c ldls/stub.c ldls/btf.c
# The test object, compiled and never linked. Ours, because docs/plan/ldls.md
# rule 1 means no .ko is committed here -- see the header of refmod.c.
@ -950,11 +952,17 @@ $(LDLS_REFMOD): ldls/test/refmod.c
@mkdir -p $(BUILD)/ldls
$(HOSTCC) -c -O1 -fno-pic -fno-asynchronous-unwind-tables -o $@ $<
ldls: $(LDLS_INSPECT) $(LDLS_RUN)
# L2. Reads struct layouts out of a kernel's own BTF, so they are never in our
# source. See ldls/btf.h.
$(LDLS_TYPES): ldls/host/types.c ldls/btf.c ldls/btf.h
@mkdir -p $(BUILD)/tools
$(HOSTCC) $(HOSTWARN) -O1 -o $@ ldls/host/types.c ldls/btf.c
ldls: $(LDLS_INSPECT) $(LDLS_RUN) $(LDLS_TYPES)
# Load our own object and check the loader got it right. No network, no QEMU,
# no module from anywhere.
ldls-check: $(LDLS_INSPECT) $(LDLS_RUN) $(LDLS_REFMOD)
ldls-check: $(LDLS_INSPECT) $(LDLS_RUN) $(LDLS_TYPES) $(LDLS_REFMOD)
$(PYTHON) tools/python/ldls_check.py $(LDLS_INSPECT) $(LDLS_REFMOD) $(LDLS_RUN)
$(MKFS): tools/mkfs.c include/fs.h include/stat.h include/param.h