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
110 lines
4.5 KiB
C
110 lines
4.5 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// The KPI: the Linux symbols a hosted driver actually calls, implemented.
|
|
//
|
|
// docs/architecture/ldls.md, "The KPI". The stub passes measured what a driver asks for
|
|
// and in what order; this is what answers, for real, against `/dev/pci` and
|
|
// `/dev/netdev`.
|
|
//
|
|
// **Rule 3 governs every file here.** A symbol exists and takes these
|
|
// arguments is a fact and is reproduced; the comment above it, the macro body
|
|
// and the implementation are expression and are never carried over. What each
|
|
// function *does* is written from documentation and from what the driver's
|
|
// behaviour requires -- which is the part BTF cannot give and is the whole of
|
|
// the real work.
|
|
//
|
|
// Organised under Linux's own header names so it is navigable: a reader who
|
|
// knows where `pci_iomap` lives in Linux knows where it is here.
|
|
|
|
#include "types.h"
|
|
|
|
// One entry per symbol LDLS can answer. The loader consults this before
|
|
// falling back to a logging stub, so an unimplemented symbol still reports
|
|
// itself rather than silently returning zero -- which is what made L1's
|
|
// measurement possible and stays useful for exactly the same reason.
|
|
struct kpi_sym {
|
|
const char *name;
|
|
void *fn;
|
|
};
|
|
|
|
extern const struct kpi_sym kpi_table[];
|
|
extern const int kpi_count;
|
|
|
|
// Look one up. Returns 0 when LDLS has no implementation, which is not an
|
|
// error: it means the caller should install a stub and find out whether the
|
|
// driver ever calls it.
|
|
void *kpi_find(const char *name);
|
|
|
|
// --- what the host gives the KPI ---------------------------------------------
|
|
//
|
|
// The KPI cannot open these for itself: `/dev/pci` is one device per process
|
|
// and the host has already claimed it. So the host hands over what it holds,
|
|
// once, before the driver runs.
|
|
|
|
struct kpi_ctx {
|
|
int pcifd; // /dev/pci, with the device claimed
|
|
int index; // which device, for the ioctls that name one
|
|
int netfd; // /dev/netdev, or -1 until register_netdev
|
|
|
|
// The BARs, as PCIIOC_INFO reported them. `pci_iomap` maps out of these and
|
|
// refuses anything else, which is the same check the kernel makes and is
|
|
// repeated here so the failure says which BAR rather than -EACCES.
|
|
unsigned long long bar_base[6];
|
|
unsigned long long bar_size[6];
|
|
unsigned int bar_flags[6];
|
|
|
|
// The mapping pci_iomap handed out, so register access can be checked
|
|
// against it. A foreign driver writing outside its own BAR is precisely the
|
|
// thing worth catching, and without an IOMMU it is also the thing nothing
|
|
// else will catch.
|
|
unsigned long io_base;
|
|
unsigned long io_len;
|
|
long io_stray; // accesses refused, so it is countable
|
|
|
|
unsigned char mac[6]; // filled in by dev_addr_mod
|
|
int have_mac;
|
|
};
|
|
|
|
extern struct kpi_ctx kpi;
|
|
|
|
// The struct offsets the KPI needs at run time, read from the kernel's own BTF
|
|
// by the host before the driver starts. See ldls/btf.h -- nothing here is
|
|
// written down, because a compiled driver and this code have to agree about
|
|
// them exactly and only the kernel that driver was built for can say.
|
|
struct kpi_layout {
|
|
unsigned int nd_netdev_ops;
|
|
unsigned int nd_dev_addr, nd_perm_addr, nd_addr_len;
|
|
unsigned int nd_mc, nd_uc;
|
|
unsigned int nd_tx, nd_num_tx, nd_real_num_tx;
|
|
unsigned int nq_size, nq_dev;
|
|
unsigned int nd_state, nd_flags;
|
|
unsigned int ndo_open, ndo_stop, ndo_start_xmit;
|
|
|
|
unsigned int skb_size;
|
|
unsigned int skb_len, skb_data, skb_head, skb_tail, skb_end, skb_dev;
|
|
};
|
|
|
|
extern struct kpi_layout kpi_l;
|
|
|
|
// The driver's NAPI poll, captured when it registers one.
|
|
//
|
|
// A NAPI driver does almost nothing in its interrupt handler: it schedules,
|
|
// and the receive happens later in the poll. LDLS has no softirq to run that
|
|
// from, so it keeps the function pointer and calls it from its own loop --
|
|
// which is what a poll loop is, arrived at from the other direction.
|
|
extern int (*kpi_napi_poll)(void *napi, int budget);
|
|
extern void *kpi_napi;
|
|
|
|
// Output. The KPI is inside a process with no libc, and every implementation
|
|
// wants to be able to say what it did.
|
|
void kpi_log(const char *s);
|
|
void kpi_lognum(long long v);
|
|
void kpi_loghex(unsigned long long v, int digits);
|
|
|
|
// Whether to narrate every call. Register access is far too noisy to print by
|
|
// default -- 538 of 8139too's 552 probe calls are ioread8/iowrite8.
|
|
extern int kpi_verbose;
|
|
|
|
// Is this a register address we handed out? Reports the first few that are
|
|
// not, then goes quiet -- a driver that is off the rails produces thousands.
|
|
int kpi_io_ok(void *addr, int width, int write);
|