ldls: a Linux driver, driving real hardware
docs/plan/ldls.md milestone L3c. ldls/kpi/ implements 41 of 8139too's 94
symbols rather than logging them: register access to a mapped BAR, the PCI
calls to /dev/pci, allocation to mmap and PCIIOC_DMA, register_netdev to
/dev/netdev.
# ldlsd /lib/modules/8139too.ko /lib/modules/btf
found 10ec:8139 at 00:05.0
bar 1 0x0000000081060000 256 mem
calling probe, with 41 of 94 symbols implemented
kpi: mapped BAR 1 -- phys 0x0000000081060000, 256 bytes
kpi: the driver read a MAC off the hardware: 52:54:00🆎cd:ef
netuser: ldls0 is a process now, 52:54:0🆎cd:ef
probe returned 0 after 0 stub call(s)
52:54:00🆎cd:ef is what the machine was configured with. Nothing invented
it: the driver bit-banged the card's EEPROM, serially, through a BAR mapped by
/dev/pci, and handed back what it found. That it matches means the loader, the
relocations, the BTF offsets, the BAR mapping and the register accessors are
all correct at once, which is why it was the thing to aim at. Zero stub calls:
every symbol probe reaches is implemented.
Two bugs, one of them the kernel's.
Mine first: pci_iomap checked a failed mmap for -1 only, while SchOS returns
-errno. It returned -13 as a pointer, the driver took that as its register
base, and the first write landed at 0x4e -- thirteen below where it meant to
be.
Underneath it, the real one. sys_mmap rounds a length up to a page, the only
granularity an MMU has, and pciuser_mmio_ok() compared that against the BAR's
own size. rtl8139's registers are 256 bytes, the request became 4096, and the
answer was -EACCES for a device the process legitimately held. A BAR smaller
than a page could never be mapped at all, and nothing had noticed because the
only consumer until now was virtio-gpu, whose BARs are large. The check is now
against the pages the BAR lies in, and the widening is named where it happens:
a sub-page BAR hands over the whole page, forced by the hardware rather than
chosen.
That is exactly what hybrid.md said a foreign driver would be good for --
"what it asks for is what a foreign driver would ask for too" -- arriving as a
concrete bug rather than a prediction.
kpi_io_ok() now bounds every ioread/iowrite against the mapping pci_iomap
returned. A stray access would otherwise be a page fault with no context, and
with no IOMMU a hosted driver's wild writes have to be caught early or not at
all. It is also what turned the fault above into two lines naming the offsets.
Not done, and said so in the output rather than only in the doc: no frame has
moved. ndo_open is never called and there is no transmit or receive path, so
ldlsd releases the interface instead of leaving it up -- a link that reports
itself up with nothing draining its queue is a black hole, and the stack would
send DHCP into it for ever. Down is the true answer until packets move. That
is L3d, now listed separately.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
3fd1cc5279
commit
42f98130c4
11 changed files with 1071 additions and 44 deletions
148
ldls/kpi/net.c
Normal file
148
ldls/kpi/net.c
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
// The network half of the KPI. See ldls/kpi/kpi.h for the provenance rule.
|
||||
//
|
||||
// `alloc_etherdev_mqs` is the interesting one and it is where BTF earns its
|
||||
// keep a second time. A driver reaches its own private area with
|
||||
// `netdev_priv(dev)`, which is an *inline function* compiled into the driver:
|
||||
// the net_device, rounded up to an alignment, and the private area after it.
|
||||
// So the size this allocates is not a free choice -- get it wrong and the
|
||||
// driver writes its state over the tail of its own control block, and nothing
|
||||
// says so until something much later reads a field that has been quietly
|
||||
// overwritten.
|
||||
//
|
||||
// The size comes from the same BTF the offsets do.
|
||||
|
||||
#include "types.h"
|
||||
#include "user.h"
|
||||
#include "fcntl.h"
|
||||
#include "posix.h"
|
||||
#include "netuser.h"
|
||||
#include "kpi.h"
|
||||
|
||||
// Filled in by the host from BTF before the driver runs. Zero means the host
|
||||
// could not find `struct net_device`, in which case allocating is a guess and
|
||||
// is refused rather than made.
|
||||
unsigned int kpi_netdev_size;
|
||||
|
||||
// NETDEV_ALIGN. Not a layout and not in BTF -- it is a constant in a header,
|
||||
// and it has been 32 for as long as x86-64 has existed. A wrong value here is
|
||||
// visible immediately as a driver that reads zeroes out of its own private
|
||||
// area, which is why it is worth stating rather than hiding in an expression.
|
||||
#define KPI_NETDEV_ALIGN 32
|
||||
|
||||
static void *g_netdev; // the one interface, for register_netdev
|
||||
|
||||
void *
|
||||
kpi_alloc_etherdev_mqs(int priv, unsigned int txqs, unsigned int rxqs)
|
||||
{
|
||||
unsigned long base, total;
|
||||
void *p;
|
||||
|
||||
(void)txqs; (void)rxqs;
|
||||
|
||||
if (kpi_netdev_size == 0) {
|
||||
kpi_log("kpi: no sizeof(struct net_device) -- cannot allocate one\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
base = (kpi_netdev_size + (KPI_NETDEV_ALIGN - 1)) & ~(unsigned long)(KPI_NETDEV_ALIGN - 1);
|
||||
total = base + (unsigned long)priv + KPI_NETDEV_ALIGN;
|
||||
|
||||
p = mmap(0, total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (p == 0 || (long)p == -1)
|
||||
return 0;
|
||||
|
||||
kpi_log(" kpi: net_device "); kpi_lognum(kpi_netdev_size);
|
||||
kpi_log(" bytes + "); kpi_lognum(priv); kpi_log(" private\n");
|
||||
|
||||
g_netdev = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
kpi_free_netdev(void *dev)
|
||||
{
|
||||
(void)dev;
|
||||
}
|
||||
|
||||
// The MAC, as the driver read it off the chip.
|
||||
//
|
||||
// This is the payoff of the whole exercise and it is worth being precise about
|
||||
// why: nothing here invented it. The driver bit-banged the card's EEPROM,
|
||||
// serially, hundreds of register accesses, and handed the result to this. If
|
||||
// it matches what the machine was actually configured with, then every layer
|
||||
// underneath -- the loader, the relocations, the BTF offsets, the BAR
|
||||
// mapping, the register accessors -- is right.
|
||||
void
|
||||
kpi_dev_addr_mod(void *dev, unsigned int off, const unsigned char *addr,
|
||||
unsigned int len)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
(void)dev;
|
||||
if (off != 0 || len != 6 || !addr)
|
||||
return;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
kpi.mac[i] = addr[i];
|
||||
kpi.have_mac = 1;
|
||||
|
||||
kpi_log(" kpi: the driver read a MAC off the hardware: ");
|
||||
for (i = 0; i < 6; i++) {
|
||||
kpi_loghex(kpi.mac[i], 2);
|
||||
if (i < 5) kpi_log(":");
|
||||
}
|
||||
kpi_log("\n");
|
||||
}
|
||||
|
||||
// Become an interface.
|
||||
//
|
||||
// /dev/netdev is the bridge docs/plan/ldls.md calls the class bridge, built
|
||||
// and proved at L3a by `netecho` with no hardware behind it at all. Here there
|
||||
// is: the same four calls, answered by a Linux driver.
|
||||
int
|
||||
kpi_register_netdev(void *dev)
|
||||
{
|
||||
struct netuser_create c;
|
||||
int i;
|
||||
|
||||
(void)dev;
|
||||
|
||||
if (!kpi.have_mac) {
|
||||
kpi_log("kpi: register_netdev before the driver found a MAC\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
kpi.netfd = open("/dev/netdev", O_RDWR);
|
||||
if (kpi.netfd < 0) {
|
||||
kpi_log("kpi: cannot open /dev/netdev\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int)sizeof(c.name); i++) c.name[i] = 0;
|
||||
for (i = 0; i < 6; i++) c.mac[i] = kpi.mac[i];
|
||||
c.pad[0] = c.pad[1] = 0;
|
||||
c.name[0] = 'l'; c.name[1] = 'd'; c.name[2] = 'l'; c.name[3] = 's';
|
||||
|
||||
if (ioctl(kpi.netfd, NETUSERIOC_CREATE, &c) < 0) {
|
||||
kpi_log("kpi: /dev/netdev is already somebody else's\n");
|
||||
close(kpi.netfd);
|
||||
kpi.netfd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
kpi_log(" kpi: registered as an interface, with the hardware's own MAC\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
kpi_unregister_netdev(void *dev)
|
||||
{
|
||||
(void)dev;
|
||||
if (kpi.netfd >= 0) {
|
||||
ioctl(kpi.netfd, NETUSERIOC_RELEASE, 0);
|
||||
close(kpi.netfd);
|
||||
kpi.netfd = -1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue