// 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; } }